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