001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.guitools.controlpanel.util;
018
019import static com.forgerock.opendj.cli.Utils.*;
020import static com.forgerock.opendj.util.OperatingSystem.*;
021
022import static org.opends.admin.ads.util.ConnectionUtils.*;
023import static org.opends.admin.ads.util.PreferredConnection.Type.*;
024import static org.opends.messages.AdminToolMessages.*;
025import static org.opends.quicksetup.Installation.*;
026
027import java.awt.Color;
028import java.awt.Component;
029import java.awt.Container;
030import java.awt.Dimension;
031import java.awt.Font;
032import java.awt.Image;
033import java.awt.Point;
034import java.awt.Toolkit;
035import java.awt.Window;
036import java.awt.event.MouseAdapter;
037import java.awt.event.MouseEvent;
038import java.io.File;
039import java.io.IOException;
040import java.io.UnsupportedEncodingException;
041import java.text.CharacterIterator;
042import java.text.StringCharacterIterator;
043import java.util.ArrayList;
044import java.util.Arrays;
045import java.util.Collection;
046import java.util.Comparator;
047import java.util.Date;
048import java.util.List;
049import java.util.logging.Logger;
050import java.util.regex.Pattern;
051
052import javax.naming.CompositeName;
053import javax.naming.InvalidNameException;
054import javax.naming.Name;
055import javax.naming.NamingEnumeration;
056import javax.naming.NamingException;
057import javax.naming.directory.SearchControls;
058import javax.naming.directory.SearchResult;
059import javax.naming.ldap.InitialLdapContext;
060import javax.naming.ldap.LdapName;
061import javax.swing.BorderFactory;
062import javax.swing.DefaultComboBoxModel;
063import javax.swing.ImageIcon;
064import javax.swing.JButton;
065import javax.swing.JCheckBox;
066import javax.swing.JComboBox;
067import javax.swing.JComponent;
068import javax.swing.JDialog;
069import javax.swing.JEditorPane;
070import javax.swing.JFrame;
071import javax.swing.JLabel;
072import javax.swing.JMenu;
073import javax.swing.JMenuItem;
074import javax.swing.JOptionPane;
075import javax.swing.JPasswordField;
076import javax.swing.JRadioButton;
077import javax.swing.JScrollPane;
078import javax.swing.JTable;
079import javax.swing.JTextArea;
080import javax.swing.JTextField;
081import javax.swing.SwingConstants;
082import javax.swing.SwingUtilities;
083import javax.swing.border.Border;
084import javax.swing.border.EmptyBorder;
085import javax.swing.border.EtchedBorder;
086import javax.swing.border.TitledBorder;
087import javax.swing.table.JTableHeader;
088import javax.swing.table.TableCellRenderer;
089import javax.swing.table.TableColumn;
090import javax.swing.table.TableColumnModel;
091
092import org.forgerock.i18n.LocalizableMessage;
093import org.forgerock.i18n.slf4j.LocalizedLogger;
094import org.forgerock.opendj.config.ConfigurationFramework;
095import org.forgerock.opendj.config.server.ConfigException;
096import org.forgerock.opendj.ldap.AttributeDescription;
097import org.forgerock.opendj.ldap.DN;
098import org.forgerock.opendj.ldap.schema.AttributeType;
099import org.forgerock.opendj.ldap.schema.MatchingRule;
100import org.forgerock.opendj.ldap.schema.Syntax;
101import org.opends.admin.ads.util.ConnectionWrapper;
102import org.opends.guitools.controlpanel.ControlPanel;
103import org.opends.guitools.controlpanel.browser.IconPool;
104import org.opends.guitools.controlpanel.datamodel.CategorizedComboBoxElement;
105import org.opends.guitools.controlpanel.datamodel.ConfigReadException;
106import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
107import org.opends.guitools.controlpanel.datamodel.CustomSearchResult;
108import org.opends.guitools.controlpanel.datamodel.MonitoringAttributes;
109import org.opends.guitools.controlpanel.datamodel.SomeSchemaElement;
110import org.opends.guitools.controlpanel.datamodel.SortableTableModel;
111import org.opends.guitools.controlpanel.datamodel.VLVIndexDescriptor;
112import org.opends.guitools.controlpanel.event.ClickTooltipDisplayer;
113import org.opends.guitools.controlpanel.event.ComboKeySelectionManager;
114import org.opends.guitools.controlpanel.event.TextComponentFocusListener;
115import org.opends.guitools.controlpanel.ui.ColorAndFontConstants;
116import org.opends.guitools.controlpanel.ui.components.LabelWithHelpIcon;
117import org.opends.guitools.controlpanel.ui.components.SelectableLabelWithHelpIcon;
118import org.opends.guitools.controlpanel.ui.renderer.AccessibleTableHeaderRenderer;
119import org.opends.quicksetup.Installation;
120import org.opends.quicksetup.ui.UIFactory;
121import org.opends.quicksetup.util.Utils;
122import org.opends.server.config.ConfigurationHandler;
123import org.opends.server.core.LockFileManager;
124import org.opends.server.schema.SchemaConstants;
125import org.opends.server.types.OpenDsException;
126import org.opends.server.types.Schema;
127import org.opends.server.util.SchemaUtils;
128import org.opends.server.util.SchemaUtils.PasswordType;
129import org.opends.server.util.ServerConstants;
130import org.opends.server.util.StaticUtils;
131
132/** A static class that provides miscellaneous functions. */
133public class Utilities
134{
135  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
136
137  private static File rootDirectory;
138  private static File instanceRootDirectory;
139
140  private static final String HTML_SPACE = " ";
141  private static final String[] attrsToObfuscate = { ServerConstants.ATTR_USER_PASSWORD };
142  private static final List<String> binarySyntaxOIDs = Arrays.asList(
143    SchemaConstants.SYNTAX_BINARY_OID,
144    SchemaConstants.SYNTAX_JPEG_OID,
145    SchemaConstants.SYNTAX_CERTIFICATE_OID,
146    SchemaConstants.SYNTAX_OCTET_STRING_OID
147  );
148
149  private static ImageIcon warningIcon;
150  private static ImageIcon requiredIcon;
151
152  private final static LocalizableMessage NO_VALUE_SET = INFO_CTRL_PANEL_NO_MONITORING_VALUE.get();
153  private final static LocalizableMessage NOT_IMPLEMENTED = INFO_CTRL_PANEL_NOT_IMPLEMENTED.get();
154
155  /**
156   * Creates a combo box.
157   *
158   * @param <T>
159   *          The combo box data type.
160   * @return a combo box.
161   */
162  public static <T> JComboBox<T> createComboBox()
163  {
164    JComboBox<T> combo = new JComboBox<>();
165    if (isMacOS())
166    {
167      combo.setOpaque(false);
168    }
169    combo.setKeySelectionManager(new ComboKeySelectionManager(combo));
170    return combo;
171  }
172
173  /**
174   * Creates a frame.
175   * @return a frame.
176   */
177  public static JFrame createFrame()
178  {
179    JFrame frame = new JFrame();
180    frame.setResizable(true);
181    org.opends.quicksetup.ui.Utilities.setFrameIcon(frame);
182    return frame;
183  }
184
185  /**
186   * Returns whether an attribute value must be obfuscated because
187   * it contains sensitive information (like passwords).
188   *
189   * @param attrName the attribute name.
190   * @param schema the schema of the server.
191   * @return {@code true} if an attribute value must be obfuscated because
192   * it contains sensitive information (like passwords) and {@code false}
193   * otherwise.
194   */
195  public static boolean mustObfuscate(String attrName, Schema schema)
196  {
197    if (schema != null)
198    {
199      return hasPasswordSyntax(attrName, schema);
200    }
201    for (String attr : attrsToObfuscate)
202    {
203      if (attr.equalsIgnoreCase(attrName))
204      {
205        return true;
206      }
207    }
208    return false;
209  }
210
211  /**
212   * Derives a color by adding the specified offsets to the base color's
213   * hue, saturation, and brightness values.   The resulting hue, saturation,
214   * and brightness values will be constrained to be between 0 and 1.
215   * @param base the color to which the HSV offsets will be added
216   * @param dH the offset for hue
217   * @param dS the offset for saturation
218   * @param dB the offset for brightness
219   * @return Color with modified HSV values
220   */
221  public static Color deriveColorHSB(Color base, float dH, float dS, float dB)
222  {
223    float hsb[] = Color.RGBtoHSB(
224        base.getRed(), base.getGreen(), base.getBlue(), null);
225
226    hsb[0] += dH;
227    hsb[1] += dS;
228    hsb[2] += dB;
229    return Color.getHSBColor(
230        hsb[0] < 0? 0 : (hsb[0] > 1? 1 : hsb[0]),
231            hsb[1] < 0? 0 : (hsb[1] > 1? 1 : hsb[1]),
232                hsb[2] < 0? 0 : (hsb[2] > 1? 1 : hsb[2]));
233
234  }
235
236  /**
237   * Displays an error dialog that contains a set of error messages.
238   * @param parentComponent the parent component relative to which the dialog
239   * will be displayed.
240   * @param errors the set of error messages that the dialog must display.
241   */
242  public static void displayErrorDialog(Component parentComponent,
243      Collection<LocalizableMessage> errors)
244  {
245    /*
246    ErrorPanel panel = new ErrorPanel("Error", errors);
247    GenericDialog dlg = new GenericDialog(null, panel);
248    dlg.setModal(true);
249    Utilities.centerGoldenMean(dlg, Utilities.getParentDialog(this));
250    dlg.setVisible(true);
251    */
252    ArrayList<String> stringErrors = new ArrayList<>();
253    for (LocalizableMessage err : errors)
254    {
255      stringErrors.add(err.toString());
256    }
257    String msg = getStringFromCollection(stringErrors, "<br>");
258    String plainText = msg.replaceAll("<br>", ServerConstants.EOL);
259    String wrappedText = wrapText(plainText, 70);
260    wrappedText = wrappedText.replaceAll(ServerConstants.EOL, "<br>");
261    JOptionPane.showMessageDialog(
262        parentComponent, "<html>"+wrappedText,
263        INFO_CTRL_PANEL_ERROR_DIALOG_TITLE.get().toString(),
264        JOptionPane.ERROR_MESSAGE);
265  }
266
267  /**
268   * Displays a confirmation dialog.
269   *
270   * @param parentComponent the parent component relative to which the dialog
271   * will be displayed.
272   * @param title the title of the dialog.
273   * @param msg the message to be displayed.
274   * @return {@code true} if the user accepts the message, {@code false} otherwise.
275   */
276  public static boolean displayConfirmationDialog(Component parentComponent,
277      LocalizableMessage title, LocalizableMessage msg)
278  {
279    String plainText = msg.toString().replaceAll("<br>", ServerConstants.EOL);
280    String wrappedText = wrapText(plainText, 70);
281    wrappedText = wrappedText.replaceAll(ServerConstants.EOL, "<br>");
282    return JOptionPane.YES_OPTION == JOptionPane.showOptionDialog(
283        parentComponent, "<html>"+wrappedText,
284        title.toString(),
285        JOptionPane.YES_NO_OPTION,
286        JOptionPane.QUESTION_MESSAGE,
287        null, // don't use a custom Icon
288        null, // the titles of buttons
289        null); // default button title
290  }
291
292  /**
293   * Displays a warning dialog.
294   * @param parentComponent the parent component relative to which the dialog
295   * will be displayed.
296   * @param title the title of the dialog.
297   * @param msg the message to be displayed.
298   */
299  public static void displayWarningDialog(Component parentComponent,
300      LocalizableMessage title, LocalizableMessage msg)
301  {
302    String plainText = msg.toString().replaceAll("<br>", ServerConstants.EOL);
303    String wrappedText = wrapText(plainText, 70);
304    wrappedText = wrappedText.replaceAll(ServerConstants.EOL, "<br>");
305    JOptionPane.showMessageDialog(
306        parentComponent, "<html>"+wrappedText,
307        title.toString(),
308        JOptionPane.WARNING_MESSAGE);
309  }
310
311
312  /**
313   * Creates a JEditorPane that displays a message.
314   * @param text the message of the editor pane in HTML format.
315   * @param font the font to be used in the message.
316   * @return a JEditorPane that displays a message.
317   */
318  public static JEditorPane makeHtmlPane(CharSequence text, Font font)
319  {
320    JEditorPane pane = new JEditorPane();
321    pane.setContentType("text/html");
322    pane.setFont(font);
323    if (text != null)
324    {
325      pane.setText(applyFont(text, font));
326    }
327    pane.setEditable(false);
328    pane.setBorder(new EmptyBorder(0, 0, 0, 0));
329    pane.setOpaque(false);
330    pane.setFocusCycleRoot(false);
331    return pane;
332  }
333
334  /**
335   * Creates a JEditorPane that displays a message.
336   * @param text the message of the editor pane in plain text format.
337   * @param font the font to be used in the message.
338   * @return a JEditorPane that displays a message.
339   */
340  public static JEditorPane makePlainTextPane(String text, Font font)
341  {
342    JEditorPane pane = new JEditorPane();
343    pane.setContentType("text/plain");
344    if (text != null)
345    {
346      pane.setText(text);
347    }
348    pane.setFont(font);
349    pane.setEditable(false);
350    pane.setBorder(new EmptyBorder(0, 0, 0, 0));
351    pane.setOpaque(false);
352    pane.setFocusCycleRoot(false);
353    return pane;
354  }
355
356  /**
357   * Returns the HTML style representation for the given font.
358   * @param font the font for which we want to get an HTML style representation.
359   * @return the HTML style representation for the given font.
360   */
361  private static String getFontStyle(Font font)
362  {
363    StringBuilder buf = new StringBuilder();
364
365    buf.append("font-family:").append(font.getName())
366        .append(";font-size:").append(font.getSize()).append("pt");
367
368    if (font.isItalic())
369    {
370      buf.append(";font-style:italic");
371    }
372
373    if (font.isBold())
374    {
375      buf.append(";font-weight:bold;");
376    }
377
378    return buf.toString();
379  }
380
381  /**
382   * Creates a titled border.
383   * @param msg the message to be displayed in the titled border.
384   * @return the created titled border.
385   */
386  public static Border makeTitledBorder(LocalizableMessage msg)
387  {
388    TitledBorder border = new TitledBorder(new EtchedBorder(),
389        " "+msg+" ");
390    border.setTitleFont(ColorAndFontConstants.titleFont);
391    border.setTitleColor(ColorAndFontConstants.foreground);
392    return border;
393  }
394
395  /**
396   * Returns a JScrollPane that contains the provided component.  The scroll
397   * pane will not contain any border.
398   * @param comp the component contained in the scroll pane.
399   * @return a JScrollPane that contains the provided component.  The scroll
400   * pane will not contain any border.
401   */
402  public static JScrollPane createBorderLessScrollBar(Component comp)
403  {
404    JScrollPane scroll = new JScrollPane(comp);
405    scroll.setBorder(new EmptyBorder(0, 0, 0, 0));
406    scroll.setViewportBorder(new EmptyBorder(0, 0, 0, 0));
407    scroll.setOpaque(false);
408    scroll.getViewport().setOpaque(false);
409    scroll.getViewport().setBackground(ColorAndFontConstants.background);
410    scroll.setBackground(ColorAndFontConstants.background);
411    UIFactory.setScrollIncrementUnit(scroll);
412    return scroll;
413  }
414
415  /**
416   * Returns a JScrollPane that contains the provided component.
417   * @param comp the component contained in the scroll pane.
418   * @return a JScrollPane that contains the provided component.
419   */
420  public static JScrollPane createScrollPane(Component comp)
421  {
422    JScrollPane scroll = new JScrollPane(comp);
423    scroll.getViewport().setOpaque(false);
424    scroll.setOpaque(false);
425    scroll.getViewport().setBackground(ColorAndFontConstants.background);
426    scroll.setBackground(ColorAndFontConstants.background);
427    UIFactory.setScrollIncrementUnit(scroll);
428    return scroll;
429  }
430
431  /**
432   * Creates a button.
433   * @param text the message to be displayed by the button.
434   * @return the created button.
435   */
436  public static JButton createButton(LocalizableMessage text)
437  {
438    JButton button = new JButton(text.toString());
439    button.setOpaque(false);
440    button.setForeground(ColorAndFontConstants.buttonForeground);
441    button.getAccessibleContext().setAccessibleName(text.toString());
442    return button;
443  }
444
445  /**
446   * Creates a radio button.
447   * @param text the message to be displayed by the radio button.
448   * @return the created radio button.
449   */
450  public static JRadioButton createRadioButton(LocalizableMessage text)
451  {
452    JRadioButton button = new JRadioButton(text.toString());
453    button.setOpaque(false);
454    button.setForeground(ColorAndFontConstants.buttonForeground);
455    button.getAccessibleContext().setAccessibleName(text.toString());
456    return button;
457  }
458
459  /**
460   * Creates a check box.
461   * @param text the message to be displayed by the check box.
462   * @return the created check box.
463   */
464  public static JCheckBox createCheckBox(LocalizableMessage text)
465  {
466    JCheckBox cb = new JCheckBox(text.toString());
467    cb.setOpaque(false);
468    cb.setForeground(ColorAndFontConstants.buttonForeground);
469    cb.getAccessibleContext().setAccessibleName(text.toString());
470    return cb;
471  }
472
473  /**
474   * Creates a menu item with the provided text.
475   * @param msg the text.
476   * @return a menu item with the provided text.
477   */
478  public static JMenuItem createMenuItem(LocalizableMessage msg)
479  {
480    return new JMenuItem(msg.toString());
481  }
482
483  /**
484   * Creates a menu with the provided text.
485   * @param msg the text.
486   * @param description the accessible description.
487   * @return a menu with the provided text.
488   */
489  public static JMenu createMenu(LocalizableMessage msg, LocalizableMessage description)
490  {
491    JMenu menu = new JMenu(msg.toString());
492    menu.getAccessibleContext().setAccessibleDescription(
493        description.toString());
494    return menu;
495  }
496
497  /**
498   * Creates a label of type 'primary' (with bigger font than usual) with no
499   * text.
500   * @return the label of type 'primary' (with bigger font than usual) with no
501   * text.
502   */
503  public static JLabel createPrimaryLabel()
504  {
505    return createPrimaryLabel(LocalizableMessage.EMPTY);
506  }
507
508  /**
509   * Creates a label of type 'primary' (with bigger font than usual).
510   * @param text the message to be displayed by the label.
511   * @return the label of type 'primary' (with bigger font than usual).
512   */
513  public static JLabel createPrimaryLabel(LocalizableMessage text)
514  {
515    JLabel label = new JLabel(text.toString());
516    label.setFont(ColorAndFontConstants.primaryFont);
517    label.setForeground(ColorAndFontConstants.foreground);
518    return label;
519  }
520
521  /**
522   * Creates a label of type 'inline help' (with smaller font).
523   * @param text the message to be displayed by the label.
524   * @return the label of type 'inline help' (with smaller font).
525   */
526  public static JLabel createInlineHelpLabel(LocalizableMessage text)
527  {
528    JLabel label = new JLabel(text.toString());
529    label.setFont(ColorAndFontConstants.inlineHelpFont);
530    label.setForeground(ColorAndFontConstants.foreground);
531    return label;
532  }
533
534  /**
535   * Creates a label of type 'title' (with bigger font).
536   * @param text the message to be displayed by the label.
537   * @return the label of type 'title' (with bigger font).
538   */
539  public static JLabel createTitleLabel(LocalizableMessage text)
540  {
541    JLabel label = new JLabel(text.toString());
542    label.setFont(ColorAndFontConstants.titleFont);
543    label.setForeground(ColorAndFontConstants.foreground);
544    return label;
545  }
546
547  /**
548   * Creates a label (with default font) with no text.
549   * @return the label (with default font) with no text.
550   */
551  public static JLabel createDefaultLabel()
552  {
553    return createDefaultLabel(LocalizableMessage.EMPTY);
554  }
555
556  /**
557   * Creates a label (with default font).
558   * @param text the message to be displayed by the label.
559   * @return the label (with default font).
560   */
561  public static JLabel createDefaultLabel(LocalizableMessage text)
562  {
563    JLabel label = new JLabel(text.toString());
564    label.setFont(ColorAndFontConstants.defaultFont);
565    label.setForeground(ColorAndFontConstants.foreground);
566    return label;
567  }
568
569  /**
570   * Returns a table created with the provided model and renderers.
571   * @param tableModel the table model.
572   * @param renderer the cell renderer.
573   * @return a table created with the provided model and renderers.
574   */
575  public static JTable createSortableTable(final SortableTableModel tableModel,
576      TableCellRenderer renderer)
577  {
578    final JTable table = new JTable(tableModel);
579    table.setShowGrid(true);
580    table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
581    table.setGridColor(ColorAndFontConstants.gridColor);
582    if (isMacOS())
583    {
584      table.getTableHeader().setBorder(
585          BorderFactory.createMatteBorder(1, 1, 0, 0,
586              ColorAndFontConstants.gridColor));
587    }
588    if (isWindows())
589    {
590      table.getTableHeader().setBorder(
591          BorderFactory.createMatteBorder(1, 1, 0, 1,
592              ColorAndFontConstants.gridColor));
593    }
594    table.getTableHeader().setDefaultRenderer(
595        new AccessibleTableHeaderRenderer(
596            table.getTableHeader().getDefaultRenderer()));
597
598    for (int i=0; i<tableModel.getColumnCount(); i++)
599    {
600      TableColumn col = table.getColumn(table.getColumnName(i));
601      col.setCellRenderer(renderer);
602    }
603    MouseAdapter listMouseListener = new MouseAdapter() {
604      @Override
605      public void mouseClicked(MouseEvent e) {
606        TableColumnModel columnModel = table.getColumnModel();
607        int viewColumn = columnModel.getColumnIndexAtX(e.getX());
608        int sortedBy = table.convertColumnIndexToModel(viewColumn);
609        if (e.getClickCount() == 1 && sortedBy != -1) {
610          tableModel.setSortAscending(!tableModel.isSortAscending());
611          tableModel.setSortColumn(sortedBy);
612          tableModel.forceResort();
613          updateTableSizes(table);
614        }
615      }
616    };
617    table.getTableHeader().addMouseListener(listMouseListener);
618    return table;
619  }
620
621  /**
622   * Creates a text area with borders similar to the ones of a text field.
623   * @param text the text of the text area.
624   * @param rows the rows of the text area.
625   * @param cols the columns of the text area.
626   * @return a text area with borders similar to the ones of a text field.
627   */
628  public static JTextArea createTextAreaWithBorder(LocalizableMessage text, int rows,
629      int cols)
630  {
631    JTextArea ta = createTextArea(text, rows, cols);
632    if (ColorAndFontConstants.textAreaBorder != null)
633    {
634      setBorder(ta, ColorAndFontConstants.textAreaBorder);
635    }
636    return ta;
637  }
638
639  /**
640   * Creates a non-editable text area.
641   * @param text the text of the text area.
642   * @param rows the rows of the text area.
643   * @param cols the columns of the text area.
644   * @return a non-editable text area.
645   */
646  public static JTextArea createNonEditableTextArea(LocalizableMessage text, int rows,
647      int cols)
648  {
649    JTextArea ta = createTextArea(text, rows, cols);
650    ta.setEditable(false);
651    ta.setOpaque(false);
652    ta.setForeground(ColorAndFontConstants.foreground);
653    return ta;
654  }
655
656  /**
657   * Creates a text area.
658   * @param text the text of the text area.
659   * @param rows the rows of the text area.
660   * @param cols the columns of the text area.
661   * @return a text area.
662   */
663  public static JTextArea createTextArea(LocalizableMessage text, int rows,
664      int cols)
665  {
666    JTextArea ta = new JTextArea(text.toString(), rows, cols);
667    ta.setFont(ColorAndFontConstants.defaultFont);
668    return ta;
669  }
670
671  /**
672   * Creates a text field.
673   * @param text the text of the text field.
674   * @param cols the columns of the text field.
675   * @return the created text field.
676   */
677  public static JTextField createTextField(String text, int cols)
678  {
679    JTextField tf = createTextField();
680    tf.setText(text);
681    tf.setColumns(cols);
682    return tf;
683  }
684
685  /**
686   * Creates a short text field.
687   * @return the created text field.
688   */
689  public static JTextField createShortTextField()
690  {
691    JTextField tf = createTextField();
692    tf.setColumns(10);
693    return tf;
694  }
695
696  /**
697   * Creates a medium sized text field.
698   * @return the created text field.
699   */
700  public static JTextField createMediumTextField()
701  {
702    JTextField tf = createTextField();
703    tf.setColumns(20);
704    return tf;
705  }
706
707  /**
708   * Creates a long text field.
709   * @return the created text field.
710   */
711  public static JTextField createLongTextField()
712  {
713    JTextField tf = createTextField();
714    tf.setColumns(30);
715    return tf;
716  }
717
718
719  /**
720   * Creates a text field with the default size.
721   * @return the created text field.
722   */
723  public static JTextField createTextField()
724  {
725    JTextField tf = new JTextField();
726    tf.addFocusListener(new TextComponentFocusListener(tf));
727    tf.setFont(ColorAndFontConstants.defaultFont);
728    return tf;
729  }
730
731  /**
732   * Creates a pasword text field.
733   * @return the created password text field.
734   */
735  public static JPasswordField createPasswordField()
736  {
737    JPasswordField pf = new JPasswordField();
738    pf.addFocusListener(new TextComponentFocusListener(pf));
739    pf.setFont(ColorAndFontConstants.defaultFont);
740    return pf;
741  }
742
743  /**
744   * Creates a pasword text field.
745   * @param cols the columns of the password text field.
746   * @return the created password text field.
747   */
748  public static JPasswordField createPasswordField(int cols)
749  {
750    JPasswordField pf = createPasswordField();
751    pf.setColumns(cols);
752    return pf;
753  }
754
755
756  /**
757   * Sets the border in a given component.  If the component already has a
758   * border, creates a compound border.
759   * @param comp the component.
760   * @param border the border to be set.
761   */
762  public static void setBorder(JComponent comp, Border border)
763  {
764    if (comp.getBorder() != null)
765    {
766      comp.setBorder(BorderFactory.createCompoundBorder(comp.getBorder(), border));
767    }
768    else
769    {
770      comp.setBorder(border);
771    }
772  }
773
774  /**
775   * Checks the size of the table and of the scroll bar where it is contained,
776   * and depending on it updates the auto resize mode.
777   * @param scroll the scroll pane containing the table.
778   * @param table the table.
779   */
780  public static void updateScrollMode(JScrollPane scroll, JTable table)
781  {
782    int width1 = table.getPreferredScrollableViewportSize().width;
783    int width2 = scroll.getViewport().getWidth();
784
785    if (width1 > width2)
786    {
787      table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
788    }
789    else
790    {
791      table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
792    }
793  }
794
795  /**
796   * Updates the size of the table rows according to the size of the
797   * rendered component.
798   * @param table the table to handle.
799   */
800  public static void updateTableSizes(JTable table)
801  {
802    updateTableSizes(table, -1);
803  }
804
805  /**
806   * Updates the size of the table rows according to the size of the
807   * rendered component.
808   * @param table the table to handle.
809   * @param rows the maximum rows to be displayed (-1 for unlimited)
810   */
811  public static void updateTableSizes(JTable table, int rows)
812  {
813    int horizontalMargin = table.getIntercellSpacing().width;
814    int verticalMargin = table.getIntercellSpacing().height;
815    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
816
817    int headerMaxHeight = 5;
818    int headerMaxWidth = 0;
819
820    JTableHeader header = table.getTableHeader();
821    if (header != null && header.isVisible())
822    {
823      for (int col=0; col<table.getColumnCount(); col++)
824      {
825        TableColumn tcol = table.getColumnModel().getColumn(col);
826        TableCellRenderer renderer = tcol.getHeaderRenderer();
827        if (renderer == null)
828        {
829          renderer = table.getTableHeader().getDefaultRenderer();
830        }
831        Component comp = renderer.getTableCellRendererComponent(table,
832            table.getModel().getColumnName(col), false, false, 0, col);
833        int colHeight = comp.getPreferredSize().height + 2 * verticalMargin;
834        if (colHeight > screenSize.height)
835        {
836          // There are some issues on Mac OS and sometimes the preferred size
837          // is too big.
838          colHeight = 0;
839        }
840        headerMaxHeight = Math.max(headerMaxHeight, colHeight);
841      }
842    }
843
844    for (int col=0; col<table.getColumnCount(); col++)
845    {
846      int colMaxWidth = 8;
847      TableColumn tcol = table.getColumnModel().getColumn(col);
848      TableCellRenderer renderer = tcol.getHeaderRenderer();
849
850      if (renderer == null && header != null)
851      {
852        renderer = header.getDefaultRenderer();
853      }
854
855      if (renderer != null)
856      {
857        Component comp = renderer.getTableCellRendererComponent(table,
858            table.getModel().getColumnName(col), false, false, 0, col);
859        colMaxWidth = comp.getPreferredSize().width  + 2 * horizontalMargin + 8;
860      }
861
862      if (colMaxWidth > screenSize.width)
863      {
864        colMaxWidth = 8;
865      }
866
867      for (int row=0; row<table.getRowCount(); row++)
868      {
869        renderer = table.getCellRenderer(row, col);
870        Component comp = table.prepareRenderer(renderer, row, col);
871        int colWidth = comp.getPreferredSize().width + 2 * horizontalMargin;
872        colMaxWidth = Math.max(colMaxWidth, colWidth);
873      }
874      tcol.setPreferredWidth(colMaxWidth);
875      headerMaxWidth += colMaxWidth;
876    }
877
878
879    if (header != null && header.isVisible())
880    {
881      header.setPreferredSize(new Dimension(headerMaxWidth, headerMaxHeight));
882    }
883
884
885    int maxRow = table.getRowHeight();
886    for (int row=0; row<table.getRowCount(); row++)
887    {
888      for (int col=0; col<table.getColumnCount(); col++)
889      {
890        TableCellRenderer renderer = table.getCellRenderer(row, col);
891        Component comp = renderer.getTableCellRendererComponent(table,
892            table.getModel().getValueAt(row, col), false, false, row, col);
893        int colHeight = comp.getPreferredSize().height + 2 * verticalMargin;
894        if (colHeight > screenSize.height)
895        {
896          colHeight = 0;
897        }
898        maxRow = Math.max(maxRow, colHeight);
899      }
900    }
901    if (maxRow > table.getRowHeight())
902    {
903      table.setRowHeight(maxRow);
904    }
905    Dimension d1;
906    if (rows == -1)
907    {
908      d1 = table.getPreferredSize();
909    }
910    else
911    {
912      d1 = new Dimension(table.getPreferredSize().width, rows * maxRow);
913    }
914    table.setPreferredScrollableViewportSize(d1);
915  }
916
917  /**
918   * Returns a String that contains the html passed as parameter with a span
919   * applied.  The span style corresponds to the Font specified as parameter.
920   * The goal of this method is to be able to specify a font for an HTML string.
921   *
922   * @param html the original html text.
923   * @param font the font to be used to generate the new HTML.
924   * @return a string that represents the original HTML with the font specified
925   * as parameter.
926   */
927  public static String applyFont(CharSequence html, Font font)
928  {
929    return "<span style=\"" + getFontStyle(font) + "\">" + html + "</span>";
930  }
931
932
933  /**
934   * Returns an ImageIcon or <CODE>null</CODE> if the path was invalid.
935   * @param path the path of the image.
936   * @param loader the class loader to use to load the image.  If
937   * <CODE>null</CODE> this class class loader will be used.
938   * @return an ImageIcon or <CODE>null</CODE> if the path was invalid.
939   */
940  public static ImageIcon createImageIcon(String path, ClassLoader loader) {
941    if (loader == null)
942    {
943      loader = ControlPanel.class.getClassLoader();
944    }
945    java.net.URL imgURL = loader.getResource(path);
946    return imgURL != null ? new ImageIcon(imgURL) : null;
947  }
948
949  /**
950   * Returns an ImageIcon or <CODE>null</CODE> if the path was invalid.
951   * @param path the path of the image.
952   * @return an ImageIcon or <CODE>null</CODE> if the path was invalid.
953   */
954  public static ImageIcon createImageIcon(String path) {
955    return createImageIcon(path, null);
956  }
957
958  /**
959   * Creates an image icon using an array of bytes that contain the image and
960   * specifying the maximum height of the image.
961   * @param bytes the byte array.
962   * @param maxHeight the maximum height of the image.
963   * @param description the description of the image.
964   * @param useFast whether a fast algorithm must be used to transform the image
965   * or an algorithm with a better result.
966   * @return an image icon using an array of bytes that contain the image and
967   * specifying the maximum height of the image.
968   */
969  public static ImageIcon createImageIcon(byte[] bytes, int maxHeight,
970      LocalizableMessage description, boolean useFast)
971  {
972    ImageIcon icon = new ImageIcon(bytes, description.toString());
973    if (maxHeight > icon.getIconHeight() || icon.getIconHeight() <= 0)
974    {
975      return icon;
976    }
977    int newHeight = maxHeight;
978    int newWidth = (newHeight * icon.getIconWidth()) / icon.getIconHeight();
979    int algo = useFast ? Image.SCALE_FAST : Image.SCALE_SMOOTH;
980    Image scaledImage = icon.getImage().getScaledInstance(newWidth, newHeight, algo);
981    return new ImageIcon(scaledImage);
982  }
983
984  /**
985   * Updates the preferred size of an editor pane.
986   * @param pane the panel to be updated.
987   * @param nCols the number of columns that the panel must have.
988   * @param plainText the text to be displayed (plain text).
989   * @param font the font to be used.
990   * @param applyBackground whether an error/warning background must be applied
991   * to the text or not.
992   */
993  public static void updatePreferredSize(JEditorPane pane, int nCols,
994      String plainText, Font font, boolean applyBackground)
995  {
996    String wrappedText = wrapText(plainText, nCols);
997    wrappedText = wrappedText.replaceAll(ServerConstants.EOL, "<br>");
998    if (applyBackground)
999    {
1000      wrappedText = UIFactory.applyErrorBackgroundToHtml(
1001          Utilities.applyFont(wrappedText, font));
1002    }
1003    JEditorPane pane2 = makeHtmlPane(wrappedText, font);
1004    pane.setPreferredSize(pane2.getPreferredSize());
1005    JFrame frame = getFrame(pane);
1006    if (frame != null && frame.isVisible())
1007    {
1008      frame.getRootPane().revalidate();
1009      frame.getRootPane().repaint();
1010    }
1011  }
1012
1013  /**
1014   * Strips any potential HTML markup from a given string.
1015   * @param s string to strip
1016   * @return resulting string
1017   */
1018  public static String stripHtmlToSingleLine(String s) {
1019    String o = null;
1020    if (s != null) {
1021      s = s.replaceAll("<br>", " ");
1022      // This is not a comprehensive solution but addresses
1023      // the few tags that we have in Resources.properties
1024      // at the moment.  Note that the following might strip
1025      // out more than is intended for non-tags like
1026      // '<your name here>' or for funky tags like
1027      // '<tag attr="1 > 0">'. See test class for cases that
1028      // might cause problems.
1029      o = s.replaceAll("\\<.*?\\>","");
1030    }
1031    return o;
1032  }
1033
1034  /**
1035   * Wraps the contents of the provided message using the specified number of
1036   * columns.
1037   * @param msg the message to be wrapped.
1038   * @param nCols the number of columns.
1039   * @return the wrapped message.
1040   */
1041  public static LocalizableMessage wrapHTML(LocalizableMessage msg, int nCols)
1042  {
1043    String s = msg.toString();
1044    StringBuilder sb = new StringBuilder();
1045    StringBuilder lastLine = new StringBuilder();
1046    int lastOpenTag = -1;
1047    boolean inTag = false;
1048    int lastSpace = -1;
1049    int lastLineLengthInLastSpace = 0;
1050    int lastLineLength = 0;
1051    for (int i=0; i<s.length() ; i++)
1052    {
1053      boolean isNormalChar = false;
1054      char c = s.charAt(i);
1055      if (c == '<')
1056      {
1057        inTag = true;
1058        lastOpenTag = i;
1059        lastLine.append(c);
1060      }
1061      else if (c == '>')
1062      {
1063        if (lastOpenTag != -1)
1064        {
1065          inTag = false;
1066          String tag = s.substring(lastOpenTag, i+1);
1067          lastOpenTag = -1;
1068          lastLine.append(c);
1069          if (isLineBreakTag(tag))
1070          {
1071            sb.append(lastLine);
1072            lastLine.delete(0, lastLine.length());
1073            lastLineLength = 0;
1074            lastSpace = -1;
1075            lastLineLengthInLastSpace = 0;
1076          }
1077        }
1078        else
1079        {
1080          isNormalChar = true;
1081        }
1082      }
1083      else if (inTag)
1084      {
1085        lastLine.append(c);
1086      }
1087      else if (c == HTML_SPACE.charAt(0))
1088      {
1089        if (s.length() >= i + HTML_SPACE.length())
1090        {
1091          if (HTML_SPACE.equalsIgnoreCase(s.substring(i, i
1092              + HTML_SPACE.length())))
1093          {
1094            if (lastLineLength < nCols)
1095            {
1096              // Only count as 1 space
1097              lastLine.append(HTML_SPACE);
1098              lastSpace = lastLine.length() - HTML_SPACE.length();
1099              lastLineLength ++;
1100              lastLineLengthInLastSpace = lastLineLength;
1101              i += HTML_SPACE.length() - 1;
1102            }
1103            else
1104            {
1105              // Insert a line break
1106              sb.append(lastLine);
1107              sb.append("<br>");
1108              lastLine.delete(0, lastLine.length());
1109              lastLineLength = 0;
1110              lastSpace = -1;
1111              lastLineLengthInLastSpace = 0;
1112              i += HTML_SPACE.length() - 1;
1113            }
1114          }
1115          else
1116          {
1117            isNormalChar = true;
1118          }
1119        }
1120        else
1121        {
1122          isNormalChar = true;
1123        }
1124      }
1125      else if (c == ' ')
1126      {
1127        if (lastLineLength < nCols)
1128        {
1129          // Only count as 1 space
1130          lastLine.append(c);
1131          lastSpace = lastLine.length() - 1;
1132          lastLineLength ++;
1133          lastLineLengthInLastSpace = lastLineLength;
1134        }
1135        else
1136        {
1137          // Insert a line break
1138          sb.append(lastLine);
1139          sb.append("<br>");
1140          lastLine.delete(0, lastLine.length());
1141          lastLineLength = 0;
1142          lastSpace = -1;
1143          lastLineLengthInLastSpace = 0;
1144        }
1145      }
1146      else
1147      {
1148        isNormalChar = true;
1149      }
1150
1151      if (isNormalChar)
1152      {
1153        if (lastLineLength < nCols)
1154        {
1155          lastLine.append(c);
1156          lastLineLength ++;
1157        }
1158        else
1159        {
1160          // Check where to insert a line break
1161          if (lastSpace != -1)
1162          {
1163            sb.append(lastLine, 0, lastSpace);
1164            sb.append("<br>");
1165            lastLine.delete(0, lastSpace + 1);
1166            lastLine.append(c);
1167            lastLineLength = lastLineLength - lastLineLengthInLastSpace + 1;
1168            lastLineLengthInLastSpace = 0;
1169            lastSpace = -1;
1170          }
1171          else
1172          {
1173            // Force the line break.
1174            sb.append(lastLine);
1175            sb.append("<br>");
1176            lastLine.delete(0, lastLine.length());
1177            lastLine.append(c);
1178            lastLineLength = 1;
1179          }
1180        }
1181      }
1182    }
1183    if (lastLine.length() > 0)
1184    {
1185      sb.append(lastLine);
1186    }
1187    return LocalizableMessage.raw(sb.toString());
1188  }
1189
1190  private static boolean isLineBreakTag(String tag)
1191  {
1192    return "<br>".equalsIgnoreCase(tag) ||
1193    "</br>".equalsIgnoreCase(tag) ||
1194    "</div>".equalsIgnoreCase(tag) ||
1195    "<p>".equalsIgnoreCase(tag) ||
1196    "</p>".equalsIgnoreCase(tag);
1197  }
1198
1199  /**
1200   * Center the component location based on its preferred size. The code
1201   * considers the particular case of 2 screens and puts the component on the
1202   * center of the left screen
1203   *
1204   * @param comp the component to be centered.
1205   */
1206  public static void centerOnScreen(Component comp)
1207  {
1208    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
1209
1210    int width = comp.getPreferredSize().width;
1211    int height = comp.getPreferredSize().height;
1212
1213    boolean multipleScreen = screenSize.width / screenSize.height >= 2;
1214
1215    if (multipleScreen)
1216    {
1217      comp.setLocation(screenSize.width / 4 - width / 2,
1218          (screenSize.height - height) / 2);
1219    } else
1220    {
1221      comp.setLocation((screenSize.width - width) / 2,
1222          (screenSize.height - height) / 2);
1223    }
1224  }
1225
1226  /**
1227   * Center the component location of the ref component.
1228   *
1229   * @param comp the component to be centered.
1230   * @param ref the component to be used as reference.
1231   */
1232  public static void centerGoldenMean(Window comp, Component ref)
1233  {
1234    comp.setLocationRelativeTo(ref);
1235    // Apply the golden mean
1236    if (ref != null && ref.isVisible())
1237    {
1238      int refY = ref.getY();
1239      int refHeight = ref.getHeight();
1240      int compHeight = comp.getPreferredSize().height;
1241
1242      int newY = refY + (int) (refHeight * 0.3819 - compHeight * 0.5);
1243      // Check that the new window will be fully visible
1244      Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
1245      if (newY > 0 && screenSize.height > newY + compHeight)
1246      {
1247        comp.setLocation(comp.getX(), newY);
1248      }
1249    }
1250  }
1251
1252  /**
1253   * Returns the parent frame of a component.  <CODE>null</CODE> if this
1254   * component is not contained in any frame.
1255   * @param comp the component.
1256   * @return the parent frame of a component.  <CODE>null</CODE> if this
1257   * component is not contained in any frame.
1258   */
1259  public static JFrame getFrame(Component comp)
1260  {
1261    Component parent = comp;
1262    while (parent != null && !(parent instanceof JFrame))
1263    {
1264      parent = parent.getParent();
1265    }
1266    return parent != null ? (JFrame) parent : null;
1267  }
1268
1269  /**
1270   * Returns the parent dialog of a component.  <CODE>null</CODE> if this
1271   * component is not contained in any dialog.
1272   * @param comp the component.
1273   * @return the parent dialog of a component.  <CODE>null</CODE> if this
1274   * component is not contained in any dialog.
1275   */
1276  public static Window getParentDialog(Component comp)
1277  {
1278    Component parent = comp;
1279    while (parent != null)
1280    {
1281      if (parent instanceof JDialog || parent instanceof JFrame)
1282      {
1283        return (Window)parent;
1284      }
1285      parent = parent.getParent();
1286    }
1287    return null;
1288  }
1289
1290  /**
1291   * Unescapes UTF-8 text and generates a String from it.
1292   * @param v the string in UTF-8 format.
1293   * @return the string with unescaped characters.
1294   */
1295  public static String unescapeUtf8(String v)
1296  {
1297    try
1298    {
1299      byte[] stringBytes = v.getBytes("UTF-8");
1300      byte[] decodedBytes = new byte[stringBytes.length];
1301      int pos = 0;
1302      for (int i = 0; i < stringBytes.length; i++)
1303      {
1304        if (stringBytes[i] == '\\'
1305                && i + 2 < stringBytes.length
1306                && StaticUtils.isHexDigit(stringBytes[i+1])
1307                && StaticUtils.isHexDigit(stringBytes[i+2]))
1308        {
1309          // Convert hex-encoded UTF-8 to 16-bit chars.
1310          byte b;
1311
1312          byte escapedByte1 = stringBytes[++i];
1313          switch (escapedByte1)
1314          {
1315          case '0':
1316            b = (byte) 0x00;
1317            break;
1318          case '1':
1319            b = (byte) 0x10;
1320            break;
1321          case '2':
1322            b = (byte) 0x20;
1323            break;
1324          case '3':
1325            b = (byte) 0x30;
1326            break;
1327          case '4':
1328            b = (byte) 0x40;
1329            break;
1330          case '5':
1331            b = (byte) 0x50;
1332            break;
1333          case '6':
1334            b = (byte) 0x60;
1335            break;
1336          case '7':
1337            b = (byte) 0x70;
1338            break;
1339          case '8':
1340            b = (byte) 0x80;
1341            break;
1342          case '9':
1343            b = (byte) 0x90;
1344            break;
1345          case 'a':
1346          case 'A':
1347            b = (byte) 0xA0;
1348            break;
1349          case 'b':
1350          case 'B':
1351            b = (byte) 0xB0;
1352            break;
1353          case 'c':
1354          case 'C':
1355            b = (byte) 0xC0;
1356            break;
1357          case 'd':
1358          case 'D':
1359            b = (byte) 0xD0;
1360            break;
1361          case 'e':
1362          case 'E':
1363            b = (byte) 0xE0;
1364            break;
1365          case 'f':
1366          case 'F':
1367            b = (byte) 0xF0;
1368            break;
1369          default:
1370            throw new RuntimeException("Unexpected byte: "+escapedByte1);
1371          }
1372
1373          byte escapedByte2 = stringBytes[++i];
1374          switch (escapedByte2)
1375          {
1376          case '0':
1377            break;
1378          case '1':
1379            b |= 0x01;
1380            break;
1381          case '2':
1382            b |= 0x02;
1383            break;
1384          case '3':
1385            b |= 0x03;
1386            break;
1387          case '4':
1388            b |= 0x04;
1389            break;
1390          case '5':
1391            b |= 0x05;
1392            break;
1393          case '6':
1394            b |= 0x06;
1395            break;
1396          case '7':
1397            b |= 0x07;
1398            break;
1399          case '8':
1400            b |= 0x08;
1401            break;
1402          case '9':
1403            b |= 0x09;
1404            break;
1405          case 'a':
1406          case 'A':
1407            b |= 0x0A;
1408            break;
1409          case 'b':
1410          case 'B':
1411            b |= 0x0B;
1412            break;
1413          case 'c':
1414          case 'C':
1415            b |= 0x0C;
1416            break;
1417          case 'd':
1418          case 'D':
1419            b |= 0x0D;
1420            break;
1421          case 'e':
1422          case 'E':
1423            b |= 0x0E;
1424            break;
1425          case 'f':
1426          case 'F':
1427            b |= 0x0F;
1428            break;
1429          default:
1430            throw new RuntimeException("Unexpected byte: "+escapedByte2);
1431          }
1432
1433          decodedBytes[pos++] = b;
1434        }
1435        else {
1436          decodedBytes[pos++] = stringBytes[i];
1437        }
1438      }
1439      return new String(decodedBytes, 0, pos, "UTF-8");
1440    }
1441    catch (UnsupportedEncodingException uee)
1442    {
1443//    This is a bug, UTF-8 should be supported always by the JVM
1444      throw new RuntimeException("UTF-8 encoding not supported", uee);
1445    }
1446  }
1447
1448  /**
1449   * Returns whether the provided strings represent the same DN.
1450   *
1451   * @param dn1 the first dn to compare.
1452   * @param dn2 the second dn to compare.
1453   * @return {@code true} if the provided strings represent the same DN, {@code false} otherwise.
1454   */
1455  public static boolean areDnsEqual(String dn1, String dn2)
1456  {
1457    try
1458    {
1459      LdapName name1 = new LdapName(dn1);
1460      LdapName name2 = new LdapName(dn2);
1461      return name1.equals(name2);
1462    } catch (Exception ex)
1463    {
1464      return false;
1465    }
1466  }
1467
1468  /**
1469   * Strings any potential "separator" from a given string.
1470   * @param s string to strip
1471   * @param separator  the separator string to remove
1472   * @return resulting string
1473   */
1474  private static String stripStringToSingleLine(String s, String separator)
1475  {
1476    return (s == null) ? null : s.replaceAll(separator, "");
1477  }
1478
1479  /** The pattern for control characters. */
1480  private final static Pattern cntrl_pattern = Pattern.compile("\\p{Cntrl}", Pattern.MULTILINE);
1481
1482  /**
1483   * Checks if a string contains control characters.
1484   * @param s : the string to check
1485   * @return true if s contains control characters, false otherwise
1486   */
1487  public static boolean hasControlCharaters(String s)
1488  {
1489    return cntrl_pattern.matcher(s).find();
1490  }
1491
1492  /**
1493   * This is a helper method that gets a String representation of the elements
1494   * in the Collection. The String will display the different elements separated
1495   * by the separator String.
1496   *
1497   * @param col
1498   *          the collection containing the String.
1499   * @param separator
1500   *          the separator String to be used.
1501   * @return the String representation for the collection.
1502   */
1503  public static String getStringFromCollection(Collection<String> col, String separator)
1504  {
1505    StringBuilder msg = new StringBuilder();
1506    for (String m : col)
1507    {
1508      if (msg.length() > 0)
1509      {
1510        msg.append(separator);
1511      }
1512      msg.append(stripStringToSingleLine(m, separator));
1513    }
1514    return msg.toString();
1515  }
1516
1517  /**
1518   * Commodity method to get the Name object representing a dn.
1519   * It is preferable to use Name objects when doing JNDI operations to avoid
1520   * problems with the '/' character.
1521   * @param dn the DN as a String.
1522   * @return a Name object representing the DN.
1523   * @throws InvalidNameException if the provided DN value is not valid.
1524   */
1525  public static Name getJNDIName(String dn) throws InvalidNameException
1526  {
1527    Name name = new CompositeName();
1528    if (dn != null && dn.length() > 0) {
1529      name.add(dn);
1530    }
1531    return name;
1532  }
1533
1534  /**
1535   * Returns the HTML representation of the 'Done' string.
1536   * @param progressFont the font to be used.
1537   * @return the HTML representation of the 'Done' string.
1538   */
1539  public static String getProgressDone(Font progressFont)
1540  {
1541    return applyFont(INFO_CTRL_PANEL_PROGRESS_DONE.get(),
1542        progressFont.deriveFont(Font.BOLD));
1543  }
1544
1545  /**
1546   * Returns the HTML representation of a message to which some points have
1547   * been appended.
1548   * @param plainText the plain text.
1549   * @param progressFont the font to be used.
1550   * @return the HTML representation of a message to which some points have
1551   * been appended.
1552   */
1553  public static String getProgressWithPoints(LocalizableMessage plainText,
1554      Font progressFont)
1555  {
1556    return applyFont(plainText.toString(), progressFont)+
1557    applyFont("&nbsp;.....&nbsp;",
1558        progressFont.deriveFont(Font.BOLD));
1559  }
1560
1561  /**
1562   * Returns the HTML representation of an error for a given text.
1563   * @param title the title.
1564   * @param titleFont the font for the title.
1565   * @param details the details.
1566   * @param detailsFont the font to be used for the details.
1567   * @return the HTML representation of an error for the given text.
1568   */
1569  public static String getFormattedError(LocalizableMessage title, Font titleFont,
1570      LocalizableMessage details, Font detailsFont)
1571  {
1572    StringBuilder buf = new StringBuilder();
1573    buf.append(UIFactory.getIconHtml(UIFactory.IconType.ERROR_LARGE))
1574        .append(HTML_SPACE).append(HTML_SPACE)
1575        .append(applyFont(title.toString(), titleFont));
1576    if (details != null)
1577    {
1578      buf.append("<br><br>")
1579      .append(applyFont(details.toString(), detailsFont));
1580    }
1581    return "<form>"+UIFactory.applyErrorBackgroundToHtml(buf.toString())+
1582    "</form>";
1583  }
1584
1585  /**
1586   * Returns the HTML representation of a success for a given text.
1587   * @param title the title.
1588   * @param titleFont the font for the title.
1589   * @param details the details.
1590   * @param detailsFont the font to be used for the details.
1591   * @return the HTML representation of a success for the given text.
1592   */
1593  public static String getFormattedSuccess(LocalizableMessage title, Font titleFont,
1594      LocalizableMessage details, Font detailsFont)
1595  {
1596    StringBuilder buf = new StringBuilder();
1597    buf.append(UIFactory.getIconHtml(UIFactory.IconType.INFORMATION_LARGE))
1598        .append(HTML_SPACE).append(HTML_SPACE)
1599        .append(applyFont(title.toString(), titleFont));
1600    if (details != null)
1601    {
1602      buf.append("<br><br>")
1603      .append(applyFont(details.toString(), detailsFont));
1604    }
1605    return "<form>"+UIFactory.applyErrorBackgroundToHtml(buf.toString())+
1606    "</form>";
1607  }
1608
1609  /**
1610   * Returns the HTML representation of a confirmation for a given text.
1611   * @param title the title.
1612   * @param titleFont the font for the title.
1613   * @param details the details.
1614   * @param detailsFont the font to be used for the details.
1615   * @return the HTML representation of a confirmation for the given text.
1616   */
1617  public static String getFormattedConfirmation(LocalizableMessage title, Font titleFont,
1618      LocalizableMessage details, Font detailsFont)
1619  {
1620    StringBuilder buf = new StringBuilder();
1621    buf.append(UIFactory.getIconHtml(UIFactory.IconType.WARNING_LARGE))
1622        .append(HTML_SPACE).append(HTML_SPACE)
1623        .append(applyFont(title.toString(), titleFont));
1624    if (details != null)
1625    {
1626      buf.append("<br><br>")
1627      .append(applyFont(details.toString(), detailsFont));
1628    }
1629    return "<form>" + buf + "</form>";
1630  }
1631
1632
1633  /**
1634   * Returns the HTML representation of a warning for a given text.
1635   * @param title the title.
1636   * @param titleFont the font for the title.
1637   * @param details the details.
1638   * @param detailsFont the font to be used for the details.
1639   * @return the HTML representation of a success for the given text.
1640   */
1641  public static String getFormattedWarning(LocalizableMessage title, Font titleFont,
1642      LocalizableMessage details, Font detailsFont)
1643  {
1644    StringBuilder buf = new StringBuilder();
1645    buf.append(UIFactory.getIconHtml(UIFactory.IconType.WARNING_LARGE))
1646        .append(HTML_SPACE).append(HTML_SPACE)
1647        .append(applyFont(title.toString(), titleFont));
1648    if (details != null)
1649    {
1650      buf.append("<br><br>")
1651      .append(applyFont(details.toString(), detailsFont));
1652    }
1653    return "<form>"+UIFactory.applyErrorBackgroundToHtml(buf.toString())+
1654    "</form>";
1655  }
1656
1657  /**
1658   * Sets the not available text to a label and associates a help icon and
1659   * a tooltip explaining that the data is not available because the server is
1660   * down.
1661   * @param l the label.
1662   */
1663  public static void setNotAvailableBecauseServerIsDown(LabelWithHelpIcon l)
1664  {
1665    l.setText(INFO_CTRL_PANEL_NOT_AVAILABLE_LONG_LABEL.get().toString());
1666    l.setHelpIconVisible(true);
1667    l.setHelpTooltip(INFO_NOT_AVAILABLE_SERVER_DOWN_TOOLTIP.get().toString());
1668  }
1669
1670  /**
1671   * Sets the not available text to a label and associates a help icon and
1672   * a tooltip explaining that the data is not available because authentication
1673   * is required.
1674   * @param l the label.
1675   */
1676  public static void setNotAvailableBecauseAuthenticationIsRequired(
1677      LabelWithHelpIcon l)
1678  {
1679    l.setText(INFO_CTRL_PANEL_NOT_AVAILABLE_LONG_LABEL.get().toString());
1680    l.setHelpIconVisible(true);
1681    l.setHelpTooltip(INFO_NOT_AVAILABLE_AUTHENTICATION_REQUIRED_TOOLTIP.get().toString());
1682  }
1683
1684  /**
1685   * Sets the not available text to a label and associates a help icon and
1686   * a tooltip explaining that the data is not available because the server is
1687   * down.
1688   * @param l the label.
1689   */
1690  public static void setNotAvailableBecauseServerIsDown(
1691      SelectableLabelWithHelpIcon l)
1692  {
1693    l.setText(INFO_CTRL_PANEL_NOT_AVAILABLE_LONG_LABEL.get().toString());
1694    l.setHelpIconVisible(true);
1695    l.setHelpTooltip(INFO_NOT_AVAILABLE_SERVER_DOWN_TOOLTIP.get().toString());
1696  }
1697
1698  /**
1699   * Sets the not available text to a label and associates a help icon and
1700   * a tooltip explaining that the data is not available because authentication
1701   * is required.
1702   * @param l the label.
1703   */
1704  public static void setNotAvailableBecauseAuthenticationIsRequired(
1705      SelectableLabelWithHelpIcon l)
1706  {
1707    l.setText(INFO_CTRL_PANEL_NOT_AVAILABLE_LONG_LABEL.get().toString());
1708    l.setHelpIconVisible(true);
1709    l.setHelpTooltip(INFO_NOT_AVAILABLE_AUTHENTICATION_REQUIRED_TOOLTIP.get().toString());
1710  }
1711
1712  /**
1713   * Updates a label by setting a warning icon and a text.
1714   * @param l the label to be updated.
1715   * @param text the text to be set on the label.
1716   */
1717  public static void setWarningLabel(JLabel l, LocalizableMessage text)
1718  {
1719    l.setText(text.toString());
1720    if (warningIcon == null)
1721    {
1722      warningIcon =
1723        createImageIcon("org/opends/quicksetup/images/warning_medium.gif");
1724      warningIcon.setDescription(
1725          INFO_WARNING_ICON_ACCESSIBLE_DESCRIPTION.get().toString());
1726      warningIcon.getAccessibleContext().setAccessibleName(
1727          INFO_WARNING_ICON_ACCESSIBLE_DESCRIPTION.get().toString());
1728    }
1729    l.setIcon(warningIcon);
1730    l.setToolTipText(text.toString());
1731    l.setHorizontalTextPosition(SwingConstants.RIGHT);
1732  }
1733
1734  /**
1735   * Sets the not available text to a label with no icon nor tooltip.
1736   * @param l the label.
1737   */
1738  public static void setNotAvailable(LabelWithHelpIcon l)
1739  {
1740    l.setText(INFO_CTRL_PANEL_NOT_AVAILABLE_LONG_LABEL.get().toString());
1741    l.setHelpIconVisible(false);
1742    l.setHelpTooltip(null);
1743  }
1744
1745  /**
1746   * Sets the a text to a label with no icon nor tooltip.
1747   * @param l the label.
1748   * @param text the text.
1749   */
1750  public static void setTextValue(LabelWithHelpIcon l, String text)
1751  {
1752    l.setText(text);
1753    l.setHelpIconVisible(false);
1754    l.setHelpTooltip(null);
1755  }
1756
1757  /**
1758   * Sets the not available text to a label with no icon nor tooltip.
1759   * @param l the label.
1760   */
1761  public static void setNotAvailable(SelectableLabelWithHelpIcon l)
1762  {
1763    l.setText(INFO_CTRL_PANEL_NOT_AVAILABLE_LONG_LABEL.get().toString());
1764    l.setHelpIconVisible(false);
1765    l.setHelpTooltip(null);
1766  }
1767
1768  /**
1769   * Sets the a text to a label with no icon nor tooltip.
1770   * @param l the label.
1771   * @param text the text.
1772   */
1773  public static void setTextValue(SelectableLabelWithHelpIcon l, String text)
1774  {
1775    l.setText(text);
1776    l.setHelpIconVisible(false);
1777    l.setHelpTooltip(null);
1778  }
1779
1780  /**
1781   * Returns the server root directory (the path where the server is installed).
1782   * <p>
1783   * Note: this method is called by SNMP code.
1784   *
1785   * @return the server root directory (the path where the server is installed).
1786   */
1787  public static File getServerRootDirectory()
1788  {
1789    if (rootDirectory == null)
1790    {
1791      // This allows testing of configuration components when the OpenDJ.jar
1792      // in the classpath does not necessarily point to the server's
1793      String installRoot = System.getProperty("org.opends.quicksetup.Root");
1794
1795      if (installRoot == null) {
1796        installRoot = getInstallPathFromClasspath();
1797      }
1798      rootDirectory = new File(installRoot);
1799    }
1800    return rootDirectory;
1801  }
1802
1803  /**
1804   * Returns the instance root directory (the path where the instance is
1805   * installed).
1806   * @param installPath The installRoot path.
1807   * @return the instance root directory (the path where the instance is
1808   *         installed).
1809   */
1810  public static File getInstanceRootDirectory(String installPath)
1811  {
1812    if (instanceRootDirectory == null)
1813    {
1814      instanceRootDirectory = new File(
1815        Utils.getInstancePathFromInstallPath(installPath));
1816    }
1817    return instanceRootDirectory;
1818  }
1819
1820  /**
1821   * Returns the path of the installation of the directory server.  Note that
1822   * this method assumes that this code is being run locally.
1823   * @return the path of the installation of the directory server.
1824   */
1825  public static String getInstallPathFromClasspath()
1826  {
1827    String installPath = null;
1828
1829    /* Get the install path from the Class Path */
1830    String sep = System.getProperty("path.separator");
1831    String[] classPaths = System.getProperty("java.class.path").split(sep);
1832    String path = getInstallPath(classPaths);
1833    if (path != null) {
1834      File f = new File(path).getAbsoluteFile();
1835      File librariesDir = f.getParentFile();
1836
1837      /*
1838       * Do a best effort to avoid having a relative representation (for
1839       * instance to avoid having ../../../).
1840       */
1841      try
1842      {
1843        installPath = librariesDir.getParentFile().getCanonicalPath();
1844      }
1845      catch (IOException ioe)
1846      {
1847        // Best effort
1848        installPath = librariesDir.getParent();
1849      }
1850    }
1851    return installPath;
1852  }
1853
1854  private static String getInstallPath(String[] classPaths)
1855  {
1856    for (String classPath : classPaths)
1857    {
1858      final String normPath = classPath.replace(File.separatorChar, '/');
1859      if (normPath.endsWith(OPENDJ_BOOTSTRAP_CLIENT_JAR_RELATIVE_PATH)
1860          || normPath.endsWith(OPENDJ_BOOTSTRAP_JAR_RELATIVE_PATH))
1861      {
1862        return classPath;
1863      }
1864    }
1865    return null;
1866  }
1867
1868  /**
1869   * Returns whether the server located in the provided path is running.
1870   *
1871   * @param serverRootDirectory the path where the server is installed.
1872   * @return {@code true} if the server located in the provided path is running,
1873   *         {@code false} otherwise.
1874   */
1875  public static boolean isServerRunning(File serverRootDirectory)
1876  {
1877    String lockFileName = ServerConstants.SERVER_LOCK_FILE_NAME + ServerConstants.LOCK_FILE_SUFFIX;
1878    String lockPathRelative = Installation.LOCKS_PATH_RELATIVE;
1879    File locksPath = new File(serverRootDirectory, lockPathRelative);
1880    String lockFile = new File(locksPath, lockFileName).getAbsolutePath();
1881    StringBuilder failureReason = new StringBuilder();
1882    try {
1883      if (LockFileManager.acquireExclusiveLock(lockFile, failureReason))
1884      {
1885        LockFileManager.releaseLock(lockFile, failureReason);
1886        return false;
1887      }
1888      return true;
1889    }
1890    catch (Throwable t) {
1891      // Assume that if we cannot acquire the lock file the
1892      // server is running.
1893      return true;
1894    }
1895  }
1896
1897  private static final String VALID_SCHEMA_SYNTAX =
1898    "abcdefghijklmnopqrstuvwxyz0123456789-";
1899
1900  /**
1901   * Returns whether the provided string can be used as objectclass name.
1902   *
1903   * @param s the string to be analyzed.
1904   * @return {@code true} if the provided string can be used as objectclass name,
1905   *         {@code false} otherwise.
1906   */
1907  private static boolean isValidObjectclassName(String s)
1908  {
1909    if (s == null || s.length() == 0)
1910    {
1911      return false;
1912    }
1913
1914    final StringCharacterIterator iter = new StringCharacterIterator(s, 0);
1915    char c = iter.first();
1916    while (c != CharacterIterator.DONE)
1917    {
1918      if (VALID_SCHEMA_SYNTAX.indexOf(Character.toLowerCase(c)) == -1)
1919      {
1920        return false;
1921      }
1922      c = iter.next();
1923    }
1924    return true;
1925  }
1926
1927  /**
1928   * Returns whether the provided string can be used as attribute name.
1929   *
1930   * @param s the string to be analyzed.
1931   * @return {@code true} if the provided string can be used as attribute name,
1932   *         {@code false} otherwise.
1933   */
1934  public static boolean isValidAttributeName(String s)
1935  {
1936    return isValidObjectclassName(s);
1937  }
1938
1939  /**
1940   * Returns the representation of the VLV index as it must be used in the
1941   * command-line.
1942   * @param index the VLV index.
1943   * @return the representation of the VLV index as it must be used in the
1944   * command-line.
1945   */
1946  public static String getVLVNameInCommandLine(VLVIndexDescriptor index)
1947  {
1948    return "vlv."+index.getName();
1949  }
1950
1951  /**
1952   * Returns a string representing the VLV index in a cell.
1953   * @param index the VLV index to be represented.
1954   * @return the string representing the VLV index in a cell.
1955   */
1956  public static String getVLVNameInCellRenderer(VLVIndexDescriptor index)
1957  {
1958    return INFO_CTRL_PANEL_VLV_INDEX_CELL.get(index.getName()).toString();
1959  }
1960
1961  private static final List<String> standardSchemaFileNames = Arrays.asList(
1962      "00-core.ldif", "01-pwpolicy.ldif", "03-changelog.ldif",
1963      "03-uddiv3.ldif", "05-solaris.ldif"
1964  );
1965
1966  private static final List<String> configurationSchemaOrigins = Arrays.asList(
1967      "OpenDJ Directory Server", "OpenDS Directory Server",
1968      "Sun Directory Server", "Microsoft Active Directory"
1969  );
1970
1971  private static final List<String> standardSchemaOrigins = Arrays.asList(
1972      "Sun Java System Directory Server", "Solaris Specific", "X.501"
1973  );
1974
1975  private static final List<String> configurationSchemaFileNames = Arrays.asList(
1976      "02-config.ldif", "06-compat.ldif"
1977  );
1978
1979  /**
1980   * Returns whether the provided schema element is part of the standard.
1981   *
1982   * @param fileElement the schema element.
1983   * @return {@code true} if the provided schema element is part of the standard,
1984   *         {@code false} otherwise.
1985   */
1986  public static boolean isStandard(SomeSchemaElement fileElement)
1987  {
1988    final String fileName = fileElement.getSchemaFile();
1989    if (fileName != null)
1990    {
1991      return standardSchemaFileNames.contains(fileName) || fileName.toLowerCase().contains("-rfc");
1992    }
1993    String xOrigin = fileElement.getOrigin();
1994    if (xOrigin != null)
1995    {
1996      return standardSchemaOrigins.contains(xOrigin) || xOrigin.startsWith("RFC ") || xOrigin.startsWith("draft-");
1997    }
1998    return false;
1999  }
2000
2001  /**
2002   * Returns whether the provided schema element is part of the configuration.
2003   *
2004   * @param fileElement the schema element.
2005   * @return {@code true} if the provided schema element is part of the configuration,
2006   *         {@code false} otherwise.
2007   */
2008  public static boolean isConfiguration(SomeSchemaElement fileElement)
2009  {
2010    String fileName = fileElement.getSchemaFile();
2011    if (fileName != null)
2012    {
2013      return configurationSchemaFileNames.contains(fileName);
2014    }
2015    String xOrigin = fileElement.getOrigin();
2016    if (xOrigin != null)
2017    {
2018      return configurationSchemaOrigins.contains(xOrigin);
2019    }
2020    return false;
2021  }
2022
2023  /**
2024   * Returns the string representation of an attribute syntax.
2025   * @param syntax the attribute syntax.
2026   * @return the string representation of an attribute syntax.
2027   */
2028  public static String getSyntaxText(Syntax syntax)
2029  {
2030    String syntaxName = syntax.getName();
2031    String syntaxOID = syntax.getOID();
2032    if (syntaxName != null)
2033    {
2034      return syntaxName + " - " + syntaxOID;
2035    }
2036    return syntaxOID;
2037  }
2038
2039  /**
2040   * Returns whether the provided attribute has image syntax.
2041   *
2042   * @param attrName the name of the attribute.
2043   * @param schema the schema.
2044   * @return {@code true} if the provided attribute has image syntax, {@code false} otherwise.
2045   */
2046  public static boolean hasImageSyntax(String attrName, Schema schema)
2047  {
2048    if ("photo".equals(AttributeDescription.valueOf(attrName).getNameOrOID()))
2049    {
2050      return true;
2051    }
2052    // Check all the attributes that we consider binaries.
2053    if (schema != null)
2054    {
2055      AttributeType attrType = AttributeDescription.valueOf(attrName, schema.getSchemaNG()).getAttributeType();
2056      if (!attrType.isPlaceHolder())
2057      {
2058        String syntaxOID = attrType.getSyntax().getOID();
2059        return SchemaConstants.SYNTAX_JPEG_OID.equals(syntaxOID);
2060      }
2061    }
2062    return false;
2063  }
2064
2065  /**
2066   * Returns whether the provided attribute has binary syntax.
2067   *
2068   * @param attrName the name of the attribute.
2069   * @param schema the schema.
2070   * @return {@code true} if the provided attribute has binary syntax, {@code false} otherwise.
2071   */
2072  public static boolean hasBinarySyntax(String attrName, Schema schema)
2073  {
2074    return attrName.toLowerCase().contains(";binary")
2075        || hasAnySyntax(attrName, schema, binarySyntaxOIDs);
2076  }
2077
2078  /**
2079   * Returns whether the provided attribute has password syntax.
2080   *
2081   * @param attrName the name of the attribute.
2082   * @param schema the schema.
2083   * @return {@code true} if the provided attribute has password syntax, {@code false} otherwise.
2084   */
2085  public static boolean hasPasswordSyntax(String attrName, Schema schema)
2086  {
2087    if (schema != null)
2088    {
2089      AttributeType attrType = AttributeDescription.valueOf(attrName, schema.getSchemaNG()).getAttributeType();
2090      if (!attrType.isPlaceHolder())
2091      {
2092        PasswordType passwordType = SchemaUtils.checkPasswordType(attrType);
2093        return passwordType.equals(PasswordType.USER_PASSWORD);
2094      }
2095    }
2096    return false;
2097  }
2098
2099  private static boolean hasAnySyntax(String attrName, Schema schema, List<String> oids)
2100  {
2101    if (schema != null)
2102    {
2103      AttributeType attrType = AttributeDescription.valueOf(attrName, schema.getSchemaNG()).getAttributeType();
2104      if (!attrType.isPlaceHolder())
2105      {
2106        return oids.contains(attrType.getSyntax().getOID());
2107      }
2108    }
2109    return false;
2110  }
2111
2112  /**
2113   * Returns the string representation of a matching rule.
2114   * @param matchingRule the matching rule.
2115   * @return the string representation of a matching rule.
2116   */
2117  public static String getMatchingRuleText(MatchingRule matchingRule)
2118  {
2119    String nameOrOID = matchingRule.getNameOrOID();
2120    String oid = matchingRule.getOID();
2121    if (!nameOrOID.equals(oid))
2122    {
2123      // This is the name only
2124      return nameOrOID + " - " + oid;
2125    }
2126    return oid;
2127  }
2128
2129  /**
2130   * Returns the InitialLdapContext to connect to the administration connector
2131   * of the server using the information in the ControlCenterInfo object (which
2132   * provides the host and administration connector port to be used) and some
2133   * LDAP credentials.
2134   * It also tests that the provided credentials have enough rights to read the
2135   * configuration.
2136   * @param controlInfo the object which provides the connection parameters.
2137   * @param bindDN the base DN to be used to bind.
2138   * @param pwd the password to be used to bind.
2139   * @return the InitialLdapContext connected to the server.
2140   * @throws NamingException if there was a problem connecting to the server
2141   * or the provided credentials do not have enough rights.
2142   * @throws ConfigReadException if there is an error reading the configuration.
2143   */
2144  public static ConnectionWrapper getAdminDirContext(ControlPanelInfo controlInfo, String bindDN, String pwd)
2145      throws NamingException, ConfigReadException
2146  {
2147    String usedUrl = controlInfo.getAdminConnectorURL();
2148    if (usedUrl == null)
2149    {
2150      throw new ConfigReadException(
2151          ERR_COULD_NOT_FIND_VALID_LDAPURL.get());
2152    }
2153
2154    // Search for the config to check that it is the directory manager.
2155    ConnectionWrapper conn = new ConnectionWrapper(
2156        usedUrl, LDAPS, bindDN, pwd, controlInfo.getConnectTimeout(), controlInfo.getTrustManager());
2157    checkCanReadConfig(conn.getLdapContext());
2158    return conn;
2159  }
2160
2161
2162  /**
2163   * Returns the InitialLdapContext to connect to the server using the
2164   * information in the ControlCenterInfo object (which provides the host, port
2165   * and protocol to be used) and some LDAP credentials.  It also tests that
2166   * the provided credentials have enough rights to read the configuration.
2167   * @param controlInfo the object which provides the connection parameters.
2168   * @param bindDN the base DN to be used to bind.
2169   * @param pwd the password to be used to bind.
2170   * @return the InitialLdapContext connected to the server.
2171   * @throws NamingException if there was a problem connecting to the server
2172   * or the provided credentials do not have enough rights.
2173   * @throws ConfigReadException if there is an error reading the configuration.
2174   */
2175  public static InitialLdapContext getUserDataDirContext(
2176      ControlPanelInfo controlInfo,
2177      String bindDN, String pwd) throws NamingException, ConfigReadException
2178  {
2179    InitialLdapContext ctx;
2180    String usedUrl;
2181    if (controlInfo.connectUsingStartTLS())
2182    {
2183      usedUrl = controlInfo.getStartTLSURL();
2184      if (usedUrl == null)
2185      {
2186        throw new ConfigReadException(
2187            ERR_COULD_NOT_FIND_VALID_LDAPURL.get());
2188      }
2189      ctx = Utils.createStartTLSContext(usedUrl,
2190          bindDN, pwd, controlInfo.getConnectTimeout(), null,
2191          controlInfo.getTrustManager(), null);
2192    }
2193    else if (controlInfo.connectUsingLDAPS())
2194    {
2195      usedUrl = controlInfo.getLDAPSURL();
2196      if (usedUrl == null)
2197      {
2198        throw new ConfigReadException(
2199            ERR_COULD_NOT_FIND_VALID_LDAPURL.get());
2200      }
2201      ctx = createLdapsContext(usedUrl,
2202          bindDN, pwd, controlInfo.getConnectTimeout(), null,
2203          controlInfo.getTrustManager(), null);
2204    }
2205    else
2206    {
2207      usedUrl = controlInfo.getLDAPURL();
2208      if (usedUrl == null)
2209      {
2210        throw new ConfigReadException(
2211            ERR_COULD_NOT_FIND_VALID_LDAPURL.get());
2212      }
2213      ctx = createLdapContext(usedUrl,
2214          bindDN, pwd, controlInfo.getConnectTimeout(), null);
2215    }
2216
2217    checkCanReadConfig(ctx);
2218    return ctx;
2219  }
2220
2221  /**
2222   * Checks that the provided connection can read cn=config.
2223   * @param ctx the connection to be tested.
2224   * @throws NamingException if an error occurs while reading cn=config.
2225   */
2226  private static void checkCanReadConfig(InitialLdapContext ctx)
2227  throws NamingException
2228  {
2229    // Search for the config to check that it is the directory manager.
2230    SearchControls searchControls = new SearchControls();
2231    searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
2232    searchControls.setReturningAttributes(new String[] { SchemaConstants.NO_ATTRIBUTES });
2233    NamingEnumeration<SearchResult> sr =
2234      ctx.search("cn=config", "objectclass=*", searchControls);
2235    try
2236    {
2237      while (sr.hasMore())
2238      {
2239        sr.next();
2240      }
2241    }
2242    finally
2243    {
2244      sr.close();
2245    }
2246  }
2247
2248  /**
2249   * Ping the specified InitialLdapContext.
2250   * This method sends a search request on the root entry of the DIT
2251   * and forward the corresponding exception (if any).
2252   * @param ctx the InitialLdapContext to be "pinged".
2253   * @throws NamingException if the ping could not be performed.
2254   */
2255  public static void pingDirContext(InitialLdapContext ctx)
2256  throws NamingException {
2257    SearchControls sc = new SearchControls(
2258        SearchControls.OBJECT_SCOPE,
2259        0, // count limit
2260        0, // time limit
2261        new String[0], // No attributes
2262        false, // Don't return bound object
2263        false // Don't dereference link
2264    );
2265    ctx.search("", "objectClass=*", sc);
2266  }
2267
2268  /**
2269   * Deletes a configuration subtree using the provided configuration handler.
2270   * @param confHandler the configuration handler to be used to delete the
2271   * subtree.
2272   * @param dn the DN of the subtree to be deleted.
2273   * @throws OpenDsException if an error occurs.
2274   * @throws ConfigException if an error occurs.
2275   */
2276  public static void deleteConfigSubtree(ConfigurationHandler confHandler, DN dn)
2277  throws OpenDsException, ConfigException
2278  {
2279    org.forgerock.opendj.ldap.Entry confEntry = confHandler.getEntry(dn);
2280    if (confEntry != null)
2281    {
2282      // Copy the values to avoid problems with this recursive method.
2283      for (DN childDN : new ArrayList<>(confHandler.getChildren(dn)))
2284      {
2285        deleteConfigSubtree(confHandler, childDN);
2286      }
2287      confHandler.deleteEntry(dn);
2288    }
2289  }
2290
2291
2292  /**
2293   * Sets the required icon to the provided label.
2294   * @param label the label to be updated.
2295   */
2296  public static void setRequiredIcon(JLabel label)
2297  {
2298    if (requiredIcon == null)
2299    {
2300      requiredIcon =
2301        createImageIcon(IconPool.IMAGE_PATH+"/required.gif");
2302      requiredIcon.setDescription(
2303          INFO_REQUIRED_ICON_ACCESSIBLE_DESCRIPTION.get().toString());
2304      requiredIcon.getAccessibleContext().setAccessibleName(
2305          INFO_REQUIRED_ICON_ACCESSIBLE_DESCRIPTION.get().toString());
2306    }
2307    label.setIcon(requiredIcon);
2308    label.setHorizontalTextPosition(SwingConstants.LEADING);
2309  }
2310
2311
2312  /**
2313   * Updates the scrolls with the provided points.
2314   * This method uses SwingUtilities.invokeLater so it can be also called
2315   * outside the event thread.
2316   * @param pos the scroll and points.
2317   */
2318  public static void updateViewPositions(final ViewPositions pos)
2319  {
2320    SwingUtilities.invokeLater(new Runnable()
2321    {
2322      @Override
2323      public void run()
2324      {
2325        for (int i=0; i<pos.size(); i++)
2326        {
2327          pos.getScrollPane(i).getViewport().setViewPosition(pos.getPoint(i));
2328        }
2329      }
2330    });
2331  }
2332
2333  /**
2334   * Gets the view positions object for the provided component.  This includes
2335   * all the scroll panes inside the provided component.
2336   * @param comp the component.
2337   * @return the view positions for the provided component.
2338   */
2339  public static ViewPositions getViewPositions(Component comp)
2340  {
2341    ViewPositions pos = new ViewPositions();
2342    if (comp instanceof Container)
2343    {
2344      updateContainedViewPositions((Container)comp, pos);
2345    }
2346    return pos;
2347  }
2348
2349  /**
2350   * Returns the scrolpane where the provided component is contained.
2351   * <CODE>null</CODE> if the component is not contained in any scrolpane.
2352   * @param comp the component.
2353   * @return the scrolpane where the provided component is contained.
2354   */
2355  public static JScrollPane getContainingScroll(Component comp)
2356  {
2357    JScrollPane scroll = null;
2358    Container parent = comp.getParent();
2359    while (scroll == null && parent != null)
2360    {
2361      if (parent instanceof JScrollPane)
2362      {
2363        scroll = (JScrollPane)parent;
2364      }
2365      else
2366      {
2367        parent = parent.getParent();
2368      }
2369    }
2370    return scroll;
2371  }
2372
2373  private static void updateContainedViewPositions(Container comp,
2374      ViewPositions pos)
2375  {
2376    if (comp instanceof JScrollPane)
2377    {
2378      JScrollPane scroll = (JScrollPane)comp;
2379      Point p = scroll.getViewport().getViewPosition();
2380      pos.add(scroll, p);
2381    }
2382    else
2383    {
2384      for (int i=0; i<comp.getComponentCount(); i++)
2385      {
2386        Component child = comp.getComponent(i);
2387        if (child instanceof Container)
2388        {
2389          updateContainedViewPositions((Container)child, pos);
2390        }
2391      }
2392    }
2393  }
2394
2395  private static Object getFirstMonitoringValue(CustomSearchResult sr, String attrName)
2396  {
2397    if (sr != null)
2398    {
2399      List<Object> values = sr.getAttributeValues(attrName);
2400      if (values != null && !values.isEmpty())
2401      {
2402        Object o = values.iterator().next();
2403        try
2404        {
2405          return Long.parseLong(o.toString());
2406        }
2407        catch (Throwable t1)
2408        {
2409          try
2410          {
2411            return Double.parseDouble(o.toString());
2412          }
2413          catch (Throwable t2)
2414          {
2415            // Cannot convert it, just return it
2416            return o;
2417          }
2418        }
2419      }
2420    }
2421    return null;
2422  }
2423
2424  /**
2425   * Returns the first value as a String for a given attribute in the provided
2426   * entry.
2427   *
2428   * @param sr
2429   *          the entry. It may be <CODE>null</CODE>.
2430   * @param attrName
2431   *          the attribute name.
2432   * @return the first value as a String for a given attribute in the provided
2433   *         entry.
2434   */
2435  public static String getFirstValueAsString(CustomSearchResult sr, String attrName)
2436  {
2437    if (sr != null)
2438    {
2439      final List<Object> values = sr.getAttributeValues(attrName);
2440      if (values != null && !values.isEmpty())
2441      {
2442        final Object o = values.get(0);
2443        if (o != null)
2444        {
2445          return String.valueOf(o);
2446        }
2447      }
2448    }
2449    return null;
2450  }
2451
2452  /**
2453   * Returns the monitoring value in a String form to be displayed to the user.
2454   * @param attr the attribute to analyze.
2455   * @param monitoringEntry the monitoring entry.
2456   * @return the monitoring value in a String form to be displayed to the user.
2457   */
2458  public static String getMonitoringValue(MonitoringAttributes attr,
2459      CustomSearchResult monitoringEntry)
2460  {
2461    String monitoringValue = getFirstValueAsString(monitoringEntry, attr.getAttributeName());
2462    if (monitoringValue == null)
2463    {
2464      return NO_VALUE_SET.toString();
2465    }
2466    else if (isNotImplemented(attr, monitoringEntry))
2467    {
2468      return NOT_IMPLEMENTED.toString();
2469    }
2470    else if (attr.isNumericDate())
2471    {
2472      if ("0".equals(monitoringValue))
2473      {
2474        return NO_VALUE_SET.toString();
2475      }
2476      Long l = Long.parseLong(monitoringValue);
2477      Date date = new Date(l);
2478      return ConfigFromDirContext.formatter.format(date);
2479    }
2480    else if (attr.isTime())
2481    {
2482      if ("-1".equals(monitoringValue))
2483      {
2484        return NO_VALUE_SET.toString();
2485      }
2486      return monitoringValue;
2487    }
2488    else if (attr.isGMTDate())
2489    {
2490      try
2491      {
2492        Date date = ConfigFromDirContext.utcParser.parse(monitoringValue);
2493        return ConfigFromDirContext.formatter.format(date);
2494      }
2495      catch (Throwable t)
2496      {
2497        return monitoringValue;
2498      }
2499    }
2500    else if (attr.isValueInBytes())
2501    {
2502      Long l = Long.parseLong(monitoringValue);
2503      long mb = l / (1024 * 1024);
2504      long kbs = (l - mb * 1024 * 1024) / 1024;
2505      return INFO_CTRL_PANEL_MEMORY_VALUE.get(mb, kbs).toString();
2506    }
2507    return monitoringValue;
2508  }
2509
2510  /**
2511   * Returns whether the provided monitoring value represents the non implemented label.
2512   *
2513   * @param attr the attribute to analyze.
2514   * @param monitoringEntry the monitoring entry.
2515   * @return {@code true} if the provided monitoring value represents the non implemented label,
2516   *         {@code false} otherwise.
2517   */
2518  private static boolean isNotImplemented(MonitoringAttributes attr,
2519      CustomSearchResult monitoringEntry)
2520  {
2521    String monitoringValue = getFirstValueAsString(monitoringEntry, attr.getAttributeName());
2522    if (attr.isNumeric() && monitoringValue != null)
2523    {
2524      try
2525      {
2526        Long.parseLong(monitoringValue);
2527        return false;
2528      }
2529      catch (Throwable t)
2530      {
2531        return true;
2532      }
2533    }
2534    return false;
2535  }
2536
2537  /**
2538   * Adds a click tool tip listener to the provided component.
2539   * @param comp the component.
2540   */
2541  public static void addClickTooltipListener(JComponent comp)
2542  {
2543    comp.addMouseListener(new ClickTooltipDisplayer());
2544  }
2545
2546  /**
2547   * Updates a combo box model with a number of items.
2548   * The method assumes that is being called from the event thread.
2549   * @param newElements the new items for the combo box model.
2550   * @param model the combo box model to be updated.
2551   */
2552  public static void updateComboBoxModel(Collection<?> newElements,
2553      DefaultComboBoxModel model)
2554  {
2555    updateComboBoxModel(newElements, model, null);
2556  }
2557
2558  /**
2559   * Updates a combo box model with a number of items.
2560   * The method assumes that is being called from the event thread.
2561   * @param newElements the new items for the combo box model.
2562   * @param model the combo box model to be updated.
2563   * @param comparator the object that will be used to compare the objects in
2564   * the model.  If <CODE>null</CODE>, the equals method will be used.
2565   */
2566  public static void updateComboBoxModel(Collection<?> newElements,
2567      DefaultComboBoxModel model,
2568      Comparator<Object> comparator)
2569  {
2570    boolean changed = newElements.size() != model.getSize();
2571    if (!changed)
2572    {
2573      int i = 0;
2574      for (Object newElement : newElements)
2575      {
2576        if (comparator != null)
2577        {
2578          changed =
2579            comparator.compare(newElement, model.getElementAt(i)) != 0;
2580        }
2581        else
2582        {
2583          changed = !newElement.equals(model.getElementAt(i));
2584        }
2585        if (changed)
2586        {
2587          break;
2588        }
2589        i++;
2590      }
2591    }
2592    if (changed)
2593    {
2594      Object selected = model.getSelectedItem();
2595      model.removeAllElements();
2596      boolean selectDefault = false;
2597      for (Object newElement : newElements)
2598      {
2599        model.addElement(newElement);
2600      }
2601      if (selected != null)
2602      {
2603        if (model.getIndexOf(selected) != -1)
2604        {
2605          model.setSelectedItem(selected);
2606        }
2607        else
2608        {
2609          selectDefault = true;
2610        }
2611      }
2612      else
2613      {
2614        selectDefault = true;
2615      }
2616      if (selectDefault)
2617      {
2618        for (int i=0; i<model.getSize(); i++)
2619        {
2620          Object o = model.getElementAt(i);
2621          if (o instanceof CategorizedComboBoxElement
2622              && ((CategorizedComboBoxElement)o).getType() == CategorizedComboBoxElement.Type.CATEGORY)
2623          {
2624            continue;
2625          }
2626          model.setSelectedItem(o);
2627          break;
2628        }
2629      }
2630    }
2631  }
2632
2633  /**
2634   * Computes the possible comparison results for monitoring information.
2635   *
2636   * @param monitor1
2637   *          the first monitor to compare
2638   * @param monitor2
2639   *          the second monitor to compare
2640   * @param possibleResults
2641   *          where possible results are output
2642   * @param attrNames
2643   *          the names for which to compute possible comparison results
2644   */
2645  public static void computeMonitoringPossibleResults(CustomSearchResult monitor1, CustomSearchResult monitor2,
2646      ArrayList<Integer> possibleResults, Collection<String> attrNames)
2647  {
2648    for (String attrName : attrNames)
2649    {
2650      int possibleResult;
2651      if (monitor1 == null)
2652      {
2653        if (monitor2 == null)
2654        {
2655          possibleResult = 0;
2656        }
2657        else
2658        {
2659          possibleResult = -1;
2660        }
2661      }
2662      else if (monitor2 == null)
2663      {
2664        possibleResult = 1;
2665      }
2666      else
2667      {
2668        Object v1 = getFirstValue(monitor1, attrName);
2669        Object v2 = getFirstValue(monitor2, attrName);
2670        if (v1 == null)
2671        {
2672          if (v2 == null)
2673          {
2674            possibleResult = 0;
2675          }
2676          else
2677          {
2678            possibleResult = -1;
2679          }
2680        }
2681        else if (v2 == null)
2682        {
2683          possibleResult = 1;
2684        }
2685        else if (v1 instanceof Number)
2686        {
2687          if (v2 instanceof Number)
2688          {
2689            if (v1 instanceof Double || v2 instanceof Double)
2690            {
2691              double n1 = ((Number) v1).doubleValue();
2692              double n2 = ((Number) v2).doubleValue();
2693              if (n1 > n2)
2694              {
2695                possibleResult = 1;
2696              }
2697              else if (n1 < n2)
2698              {
2699                possibleResult = -1;
2700              }
2701              else
2702              {
2703                possibleResult = 0;
2704              }
2705            }
2706            else
2707            {
2708              long n1 = ((Number) v1).longValue();
2709              long n2 = ((Number) v2).longValue();
2710              if (n1 > n2)
2711              {
2712                possibleResult = 1;
2713              }
2714              else if (n1 < n2)
2715              {
2716                possibleResult = -1;
2717              }
2718              else
2719              {
2720                possibleResult = 0;
2721              }
2722            }
2723          }
2724          else
2725          {
2726            possibleResult = 1;
2727          }
2728        }
2729        else if (v2 instanceof Number)
2730        {
2731          possibleResult = -1;
2732        }
2733        else
2734        {
2735          possibleResult = v1.toString().compareTo(v2.toString());
2736        }
2737      }
2738      possibleResults.add(possibleResult);
2739    }
2740  }
2741
2742  private static Object getFirstValue(CustomSearchResult monitor, String attrName)
2743  {
2744    for (String attr : monitor.getAttributeNames())
2745    {
2746      if (attr.equalsIgnoreCase(attrName))
2747      {
2748        return getFirstMonitoringValue(monitor, attrName);
2749      }
2750    }
2751    return null;
2752  }
2753
2754  /**
2755   * Throw the first exception of the list (if any).
2756   *
2757   * @param <E>
2758   *          The exception type
2759   * @param exceptions
2760   *          A list of exceptions.
2761   * @throws E
2762   *           The first element of the provided list (if the list is not
2763   *           empty).
2764   */
2765  public static <E extends Exception> void throwFirstFrom(List<? extends E> exceptions) throws E
2766  {
2767    if (!exceptions.isEmpty())
2768    {
2769      throw exceptions.get(0);
2770    }
2771  }
2772
2773  /** Initialize the configuration framework. */
2774  public static void initializeConfigurationFramework()
2775  {
2776    if (!ConfigurationFramework.getInstance().isInitialized())
2777    {
2778      try
2779      {
2780        // Initialize configuration framework without logging anything.
2781        final Logger configFrameworkLogger = Logger.getLogger("com.forgerock.opendj.ldap.config.config");
2782        configFrameworkLogger.setUseParentHandlers(false);
2783        ConfigurationFramework.getInstance().initialize();
2784        configFrameworkLogger.setUseParentHandlers(true);
2785      }
2786      catch (ConfigException e)
2787      {
2788        final LocalizableMessage message = ERROR_CTRL_PANEL_INITIALIZE_CONFIG_OFFLINE.get(e.getLocalizedMessage());
2789        logger.error(message);
2790        throw new RuntimeException(message.toString(), e);
2791      }
2792    }
2793  }
2794
2795}