001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU General
007: * Public License Version 2 only ("GPL") or the Common Development and Distribution
008: * License("CDDL") (collectively, the "License"). You may not use this file except in
009: * compliance with the License. You can obtain a copy of the License at
010: * http://www.netbeans.org/cddl-gplv2.html or nbbuild/licenses/CDDL-GPL-2-CP. See the
011: * License for the specific language governing permissions and limitations under the
012: * License. When distributing the software, include this License Header Notice in
013: * each file and include the License file at nbbuild/licenses/CDDL-GPL-2-CP. Sun
014: * designates this particular file as subject to the "Classpath" exception as
015: * provided by Sun in the GPL Version 2 section of the License file that
016: * accompanied this code. If applicable, add the following below the License Header,
017: * with the fields enclosed by brackets [] replaced by your own identifying
018: * information: "Portions Copyrighted [year] [name of copyright owner]"
019: *
020: * Contributor(s):
021: *
022: * The Original Software is NetBeans. The Initial Developer of the Original Software
023: * is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun Microsystems, Inc. All
024: * Rights Reserved.
025: *
026: * If you wish your version of this file to be governed by only the CDDL or only the
027: * GPL Version 2, indicate your decision by adding "[Contributor] elects to include
028: * this software in this distribution under the [CDDL or GPL Version 2] license." If
029: * you do not indicate a single choice of license, a recipient has the option to
030: * distribute your version of this file under either the CDDL, the GPL Version 2 or
031: * to extend the choice of license to its licensees as provided above. However, if
032: * you add GPL Version 2 code and therefore, elected the GPL Version 2 license, then
033: * the option applies only if the new code is made subject to such option by the
034: * copyright holder.
035: */
036:
037: package org.netbeans.installer.utils;
038:
039: import java.awt.GraphicsEnvironment;
040: import java.awt.HeadlessException;
041: import java.awt.Toolkit;
042: import java.security.cert.Certificate;
043: import java.security.Principal;
044: import java.security.cert.X509Certificate;
045: import java.text.DateFormat;
046: import java.util.Date;
047: import java.util.Properties;
048: import javax.swing.JFileChooser;
049: import javax.swing.JFrame;
050: import javax.swing.JOptionPane;
051: import javax.swing.JProgressBar;
052: import javax.swing.LookAndFeel;
053: import javax.swing.UIManager;
054: import javax.swing.UnsupportedLookAndFeelException;
055: import org.netbeans.installer.utils.exceptions.InitializationException;
056: import org.netbeans.installer.utils.helper.Platform;
057: import org.netbeans.installer.utils.helper.UiMode;
058:
059: import static javax.swing.JOptionPane.YES_NO_OPTION;
060: import static javax.swing.JOptionPane.YES_OPTION;
061: import static javax.swing.JOptionPane.NO_OPTION;
062: import static org.netbeans.installer.utils.SystemUtils.getCurrentPlatform;
063:
064: /**
065: *
066: * @author Kirill Sorokin
067: */
068: public final class UiUtils {
069: /////////////////////////////////////////////////////////////////////////////////
070: // Static
071: private static boolean lookAndFeelInitialized = false;
072: private static LookAndFeelType lookAndFeelType = null;
073:
074: public static void showMessageDialog(final String message,
075: final String title, final MessageType messageType) {
076: try {
077: initializeLookAndFeel();
078: } catch (InitializationException e) {
079: ErrorManager.notifyWarning(e.getMessage(), e.getCause());
080: }
081:
082: switch (UiMode.getCurrentUiMode()) {
083: case SWING:
084: int intMessageType = JOptionPane.INFORMATION_MESSAGE;
085: if (messageType == MessageType.WARNING) {
086: intMessageType = JOptionPane.WARNING_MESSAGE;
087: } else if (messageType == MessageType.ERROR) {
088: intMessageType = JOptionPane.ERROR_MESSAGE;
089: } else if (messageType == MessageType.CRITICAL) {
090: intMessageType = JOptionPane.ERROR_MESSAGE;
091: }
092:
093: JOptionPane.showMessageDialog(null, message, title,
094: intMessageType);
095: break;
096: case SILENT:
097: System.err.println(message);
098: break;
099: }
100: }
101:
102: public static boolean showYesNoDialog(final String title,
103: final String message) {
104: final int result = JOptionPane.showConfirmDialog(null, message,
105: title, YES_NO_OPTION);
106:
107: return result == YES_OPTION;
108: }
109:
110: public static CertificateAcceptanceStatus showCertificateAcceptanceDialog(
111: final Certificate[] certificates, final int chainStart,
112: final int chainEnd, final boolean rootCaIsNotValid,
113: final boolean timeIsNotValid, final Date timestamp,
114: final String description) {
115: if (certificates[chainStart] instanceof X509Certificate
116: && certificates[chainEnd - 1] instanceof X509Certificate) {
117: final X509Certificate firstCert = (X509Certificate) certificates[chainStart];
118: final X509Certificate lastCert = (X509Certificate) certificates[chainEnd - 1];
119:
120: final Principal subject = firstCert.getSubjectDN();
121: final Principal issuer = lastCert.getIssuerDN();
122:
123: // extract subject & issuer's name
124: final String subjectName = extractName(subject.getName(),
125: "CN=", "Unknown Subject");
126: final String issuerName = extractName(issuer.getName(),
127: "O=", "Unknown Issuer");
128:
129: // dialog caption
130: String caption = null;
131: String body = "";
132:
133: // check if this is the case when both - the root CA and time of
134: // signing is valid:
135: if ((!rootCaIsNotValid) && (!timeIsNotValid)) {
136: caption = StringUtils
137: .format(
138: "The digital signature of {0} has been verified.",
139: description);
140:
141: body += "The digital signature has been validated by a trusted source. "
142: + "The security certificate was issued by a company that is trusted";
143:
144: // for timestamp info, add a message saying that certificate was
145: // valid at the time of signing. And display date of signing.
146: if (timestamp != null) {
147: // get the right date format for timestamp
148: final DateFormat df = DateFormat
149: .getDateTimeInstance(DateFormat.LONG,
150: DateFormat.LONG);
151: body += StringUtils
152: .format(
153: " and was valid at the time of signing on {0}.",
154: df.format(timestamp));
155: } else {
156: // add message about valid time of signing:
157: body += ", has not expired and is still valid.";
158: }
159:
160: // we should add one more message here - disclaimer we used
161: // to have. This is to be displayed in the "All trusted"
162: // case in the More Information dialog.
163: body += StringUtils
164: .format(
165: "Caution: \"{0}\" asserts that this content is safe. You should only accept this content if you trust \"{1}\" to make that assertion.",
166: subjectName, subjectName);
167: } else {
168: // this is the case when either publisher or time of signing
169: // is invalid - check and add corresponding messages to
170: // appropriate message arrays.
171:
172: // If root CA is not valid, add a caption and a message to the
173: // securityAlerts array.
174: if (rootCaIsNotValid) {
175: // Use different caption text for https and signed content
176: caption = StringUtils
177: .format(
178: "The digital signature of {0} cannot be verified.",
179: description);
180:
181: body += "The digital signature cannot be verified by a trusted source. "
182: + "Only continue if you trust the origin of the file. "
183: + "The security certificate was issued by a company that is not trusted.";
184: } else {
185: caption = StringUtils
186: .format(
187: "The digital signature of {0} has been verified.",
188: description);
189:
190: // Same details for both
191: body += "The security certificate was issued by a company that is trusted.";
192: }
193:
194: // now check if time of signing is valid.
195: if (timeIsNotValid) {
196: // if no warnings yet, add the one that will show with the
197: // bullet in security warning dialog:
198: body += "The digital signature was generated with a trusted certificate but has expired or is not yet valid";
199: } else {
200: // for timestamp info, add a message saying that certificate
201: // was valid at the time of signing
202: if (timestamp != null) {
203: // get the right date format for timestamp
204: final DateFormat df = DateFormat
205: .getDateTimeInstance(DateFormat.LONG,
206: DateFormat.LONG);
207: body += StringUtils
208: .format(
209: "The security certificate was valid at the time of signing on {0}.",
210: df.format(timestamp));
211: } else {
212: body += "The security certificate has not expired and is still valid.";
213: }
214: }
215: }
216:
217: String message = StringUtils
218: .format(
219: "<html><b>{0}</b><br>Subject: {1}<br>Issuer: {2}<br><br>{3}<br><br>Click OK to accept the certificate permanently, No to accept it temporary for this session, Cancel to reject the certificate.",
220: caption, subjectName, issuerName, body);
221:
222: int option = JOptionPane.showConfirmDialog(null, message);
223: if (option == JOptionPane.OK_OPTION) {
224: return CertificateAcceptanceStatus.ACCEPT_PERMANENTLY;
225: } else {
226: return CertificateAcceptanceStatus.DENY;
227: }
228: }
229:
230: return CertificateAcceptanceStatus.DENY;
231: }
232:
233: public static void initializeLookAndFeel()
234: throws InitializationException {
235: if (lookAndFeelInitialized) {
236: return;
237: }
238:
239: try {
240: LogManager.log("... initializing look and feel");
241: LogManager.indent();
242: switch (UiMode.getCurrentUiMode()) {
243: case SWING:
244: String className = System
245: .getProperty(LAF_CLASS_NAME_PROPERTY);
246: if (className == null) {
247: LogManager
248: .log("... custom look and feel class name was not specified, using system default");
249: className = UiUtils
250: .getDefaultLookAndFeelClassName();
251: }
252:
253: LogManager.log("... class name: " + className);
254:
255: if (Boolean.getBoolean(LAF_DECORATED_WINDOWS_PROPERTY)) {
256: JFrame.setDefaultLookAndFeelDecorated(true);
257: }
258:
259: try {
260: try {
261: // this helps to avoid some GTK L&F bugs for some locales
262: LogManager.log("... get installed L&Fs");
263: UIManager.getInstalledLookAndFeels();
264: LogManager.log("... set specified L&F");
265: UIManager.setLookAndFeel(className);
266: LogManager.log("... check headless");
267: if (GraphicsEnvironment.isHeadless()) {
268: HeadlessException e = new HeadlessException();
269: System.err.println(e.getMessage());
270: throw new InitializationException(
271: ResourceUtils.getString(
272: UiUtils.class,
273: RESOURCE_FAILED_TO_INIT_UI),
274: e);
275: }
276: if (SystemUtils.isWindows()) {
277: // workaround for the issue with further using JFileChooser
278: // in case of missing system icons
279: // Java Issue :
280: // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6210674
281: // NBI Issue :
282: // http://www.netbeans.org/issues/show_bug.cgi?id=105065
283: // it also a workaround for two more bugs
284: // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6449933
285: // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6489447
286: LogManager
287: .log("... creating JFileChooser object to check possible issues with UI");
288: new JFileChooser();
289:
290: LogManager
291: .log("... getting default Toolkit to check possible issues with UI");
292: Toolkit.getDefaultToolkit();
293:
294: // workaround for JDK issue with JProgressBar using StyleXP
295: // http://www.netbeans.org/issues/show_bug.cgi?id=106876
296: // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6337517
297: LogManager
298: .log("... creating JProgressBar object to check possible issues with UI");
299: new JProgressBar().getMaximumSize();
300:
301: LogManager.log("... all UI checks done");
302: }
303: LogManager.log("... L&F is set");
304: } catch (Throwable e) {
305: // we're catching Throwable here as pretty much anything can happen
306: // while setting the look and feel and we have no control over it
307: // if something wrong happens we should fall back to the default
308: // cross-platform look and feel which is assumed to be working
309: // correctly
310: LogManager
311: .log(
312: "... could not activate defined L&F, initializing cross-platfrom one",
313: e);
314: if (e instanceof InternalError) {
315: System.err.println(e.getMessage());
316: } else if (e instanceof ExceptionInInitializerError) {
317: final Throwable cause = e.getCause();
318:
319: if ((cause != null)
320: && (cause instanceof HeadlessException)) {
321: System.err.println(cause.getMessage());
322: }
323: }
324:
325: className = UIManager
326: .getCrossPlatformLookAndFeelClassName();
327: LogManager
328: .log("... cross-platform L&F class-name : "
329: + className);
330:
331: UIManager.setLookAndFeel(className);
332:
333: if (System.getProperty(LAF_CLASS_NAME_PROPERTY) != null) {
334: // Throw exception only if user specified custom L&F,
335: // otherwise just go to initialization of cross-platfrom L&F
336: // (Exception e is already logged above)
337: // See also http://www.netbeans.org/issues/show_bug.cgi?id=122557
338: // This exception would be thrown only if cross-platform LAF is successfully installed
339: throw new InitializationException(
340: ResourceUtils
341: .getString(UiUtils.class,
342: RESOURCE_FAILED_TO_ACTIVATE_DEFINED_LAF),
343: e);
344: }
345: }
346: } catch (NoClassDefFoundError e) {
347: throw new InitializationException(
348: ResourceUtils
349: .getString(UiUtils.class,
350: RESOURCE_FAILED_TO_ACTIVATE_CROSSPLATFORM_LAF),
351: e);
352: } catch (ClassNotFoundException e) {
353: throw new InitializationException(
354: ResourceUtils
355: .getString(UiUtils.class,
356: RESOURCE_FAILED_TO_ACTIVATE_CROSSPLATFORM_LAF),
357: e);
358: } catch (InstantiationException e) {
359: throw new InitializationException(
360: ResourceUtils
361: .getString(UiUtils.class,
362: RESOURCE_FAILED_TO_ACTIVATE_CROSSPLATFORM_LAF),
363: e);
364: } catch (IllegalAccessException e) {
365: throw new InitializationException(
366: ResourceUtils
367: .getString(UiUtils.class,
368: RESOURCE_FAILED_TO_ACTIVATE_CROSSPLATFORM_LAF),
369: e);
370: } catch (UnsupportedLookAndFeelException e) {
371: throw new InitializationException(
372: ResourceUtils
373: .getString(UiUtils.class,
374: RESOURCE_FAILED_TO_ACTIVATE_CROSSPLATFORM_LAF),
375: e);
376: }
377: break;
378: }
379: } finally {
380: LogManager.unindent();
381: LogManager.log("... initializing L&F finished");
382: lookAndFeelInitialized = true;
383: lookAndFeelType = getLAF();
384: }
385: }
386:
387: public static String getDefaultLookAndFeelClassName() {
388: switch (UiMode.getCurrentUiMode()) {
389: case SWING:
390: String className = UIManager
391: .getSystemLookAndFeelClassName();
392:
393: // if the default look and feel is the cross-platform one, we might
394: // need to correct this choice. E.g. - KDE, where GTK look and feel
395: // would be much more appropriate
396: if (className.equals(UIManager
397: .getCrossPlatformLookAndFeelClassName())) {
398:
399: // if the current platform is Linux and the desktop manager is
400: // KDE, then we should try to use the GTK look and feel
401: try {
402: if (getCurrentPlatform().isCompatibleWith(
403: Platform.LINUX)
404: && (System.getenv("KDE_FULL_SESSION") != null)) {
405: // check whether the GTK look and feel class is
406: // available -- we'll get CNFE if it is not and it will
407: // not be set
408: Class
409: .forName("com.sun.java.swing.plaf.gtk.GTKLookAndFeel");
410:
411: className = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
412: }
413: } catch (ClassNotFoundException e) {
414: ErrorManager.notifyDebug(ResourceUtils
415: .getString(UiUtils.class,
416: RESOURCE_FAILED_TO_FORCE_GTK), e);
417: }
418: }
419:
420: return className;
421: default:
422: return null;
423: }
424: }
425:
426: public static final LookAndFeelType getLAF() {
427: if (lookAndFeelType == null) {
428: try {
429: initializeLookAndFeel();
430: } catch (InitializationException e) {
431: LogManager.log(e);
432: }
433: lookAndFeelType = LookAndFeelType.DEFAULT;
434:
435: if (UiMode.getCurrentUiMode() == UiMode.SWING) {
436: LookAndFeel laf = UIManager.getLookAndFeel();
437: if (laf != null) {
438: String id = laf.getID();
439: if (id.equals("Windows")) {
440: final Object object = Toolkit
441: .getDefaultToolkit()
442: .getDesktopProperty(
443: WINDOWS_XP_THEME_MARKER_PROPERTY);
444: boolean xpThemeActive = false;
445: if (object != null) {
446: xpThemeActive = (Boolean) object;
447: }
448: lookAndFeelType = (xpThemeActive) ? LookAndFeelType.WINDOWS_XP
449: : LookAndFeelType.WINDOWS_CLASSIC;
450: } else if (id.equals("GTK")) {
451: lookAndFeelType = LookAndFeelType.GTK;
452: } else if (id.equals("Motif")) {
453: lookAndFeelType = LookAndFeelType.MOTIF;
454: } else if (id.equals("Metal")) {
455: lookAndFeelType = LookAndFeelType.METAL;
456: } else if (id.equals("Aqua")) {
457: lookAndFeelType = LookAndFeelType.AQUA;
458: }
459: }
460: }
461: }
462: return lookAndFeelType;
463: }
464:
465: // private //////////////////////////////////////////////////////////////////////
466: private static String extractName(final String nameString,
467: final String prefix, final String defaultValue) {
468: int i = nameString.indexOf(prefix);
469: int j = 0;
470:
471: if (i < 0) {
472: return defaultValue;
473: } else {
474: try {
475: // shift to the beginning of the prefix text
476: i = i + prefix.length();
477:
478: // check if it begins with a quote
479: if (nameString.charAt(i) == '\"') {
480: // skip the quote
481: i = i + 1;
482:
483: // search for another quote
484: j = nameString.indexOf('\"', i);
485: } else {
486:
487: // no quote, so search for comma
488: j = nameString.indexOf(',', i);
489: }
490:
491: if (j < 0) {
492: return nameString.substring(i);
493: } else {
494: return nameString.substring(i, j);
495: }
496: } catch (IndexOutOfBoundsException e) {
497: return defaultValue;
498: }
499: }
500: }
501:
502: /////////////////////////////////////////////////////////////////////////////////
503: // Instance
504: private UiUtils() {
505: // does nothing
506: }
507:
508: /////////////////////////////////////////////////////////////////////////////////
509: // Inner Classes
510: public static enum CertificateAcceptanceStatus {
511: ACCEPT_PERMANENTLY, ACCEPT_FOR_THIS_SESSION, DENY
512: }
513:
514: public static enum MessageType {
515: INFORMATION, WARNING, ERROR, CRITICAL
516: }
517:
518: public enum LookAndFeelType {
519: WINDOWS_XP("win.xp"), WINDOWS_CLASSIC("win.classic"), MOTIF(
520: "motif"), GTK("gtk"), METAL("metal"), AQUA("aqua"), DEFAULT(
521: "default");
522:
523: private String name;
524:
525: public String toString() {
526: return name;
527: }
528:
529: private LookAndFeelType(String name) {
530: this .name = name;
531: }
532: };
533:
534: public static int getDimension(Properties props,
535: final String defaultPropertyName, final int defaultValue) {
536: int dimension = defaultValue;
537: String propertyName = defaultPropertyName;
538: if (props.getProperty(propertyName + "." + UiUtils.getLAF()) != null) {
539: propertyName = propertyName + "." + UiUtils.getLAF();
540: }
541:
542: if (props.getProperty(propertyName) != null) {
543: try {
544: dimension = Integer.parseInt(props.getProperty(
545: propertyName).trim());
546: } catch (NumberFormatException e) {
547: final String warning = ResourceUtils.getString(
548: UiUtils.class,
549: RESOURCE_FAILED_TO_PARSE_SYSTEM_PROPERTY,
550: propertyName, props.getProperty(propertyName));
551:
552: ErrorManager.notifyWarning(warning, e);
553: }
554: }
555: return dimension;
556: }
557:
558: /////////////////////////////////////////////////////////////////////////////////
559: // Constants
560:
561: /**
562: * Name of the system property, which contains the look and feel class name that
563: * should be used by the wizard.
564: */
565: public static final String LAF_CLASS_NAME_PROPERTY = "nbi.look.and.feel"; // NOI18N
566:
567: /**
568: * Name of the system property, which tells the UiUtils whether the wizard
569: * windows should be decorated by the current look and feel or the system
570: * window manager.
571: */
572: public static final String LAF_DECORATED_WINDOWS_PROPERTY = "nbi.look.and.feel.decorate.windows"; // NOI18N
573:
574: public static final String WINDOWS_XP_THEME_MARKER_PROPERTY = "win.xpstyle.themeActive"; // NOI18N
575: /**
576: * Name of a resource bundle entry.
577: */
578: private static final String RESOURCE_FAILED_TO_PARSE_SYSTEM_PROPERTY = "UI.error.failed.to.parse.property"; // NOI18N
579: private static final String RESOURCE_FAILED_TO_ACTIVATE_CROSSPLATFORM_LAF = "UI.error.failed.to.activate.crossplatform.laf"; // NOI18N
580: private static final String RESOURCE_FAILED_TO_ACTIVATE_DEFINED_LAF = "UI.error.failed.to.activate.defined.laf";//NOI18N
581: private static final String RESOURCE_FAILED_TO_INIT_UI = "UI.error.failed.to.init.ui";//NOI18N
582: private static final String RESOURCE_FAILED_TO_FORCE_GTK = "UI.error.failed.to.force.gtk";//NOI18N
583: }
|