/******************************************************************************* * Copyright (c) 2012 Google, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Google, Inc. - initial API and implementation *******************************************************************************/ package com.windowtester.internal.product; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.Field; import java.net.URL; import java.util.GregorianCalendar; import java.util.Properties; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.PluginVersionIdentifier; import com.windowtester.internal.debug.Logger; import com.windowtester.runtime.util.PluginUtilities; /** * Shared behavior for all products. * <p> * * @author Dan Rubel */ public class WindowTesterProProduct implements IProduct { private static final String ECLIPSE_VERSION_KEY = "eclipse.version"; private static final String TARGET_PROPERTIES = "target.properties"; private static final String PLUGIN_PROPERTIES = "plugin.properties"; private static final String BUILD_KEY = "build"; private static final String PRODUCT_ID = "WindowTesterPro"; private static final String WTPRO_ID = PRODUCT_ID; private static final String[] PRO_IDS = new String[] {WTPRO_ID}; private static final String NAME = "WindowTester Pro"; private static final String DESCRIPTION = NAME + " creates and executes system level unit tests for Swing, SWT and Eclipse RCP based user interfaces"; private static final String PLUGIN_ID = "com.windowtester.runtime"; private static final String[] PLUGIN_IDS = new String[] { PLUGIN_ID }; private static final String DEVELOPMENT_BUILD_NUM = "${build_num}"; /** * The product version or <code>null</code> if it has not yet been initialized by * {@link #getVersion()}. Typically the product version is derived from the version * of the primary plugin. */ private PluginVersionIdentifier version; /** * The product build or <code>null</code> if it has not yet been initialized by * {@link #getBuild()}. Typically the product build is read from the * plugin.properties file of the primary plugin. */ private String build; /** * The product build date or <code>null</code> if it has not yet been initialized by * {@link #getBuildDate()}. Typically the product build is read from the * plugin.properties file of the primary plugin and then translated into a date. * If the build cannot be translated into a date, then this field will have an */ private GregorianCalendar buildDate; /** * The expected version of Eclipse for which this product was built or * <code>null</code> if it has not yet been initialized by * {@link #getExpectedEclipseVersion()}. Typically the expected Eclipse version is * read from the "eclipse.version" key in the target.properties file of the primary * plugin and then translated into a version. If the version cannot be determined for * some reason such as the file does not exist or the string cannot be converted to a * version, then this field will have the value {@link IProduct#UNKNOWN_VERSION}. */ private PluginVersionIdentifier expectedEclipseVersion; private static WindowTesterProProduct instance; public static WindowTesterProProduct getInstance() { if (instance == null) instance = new WindowTesterProProduct(); return instance; } private WindowTesterProProduct() { } /** * Answer the all known products. * <p> * WARNING! The array used by this method ({@linkplain #_products}) is constructed * as a result of each {@linkplain BaseProduct} subclass being instantiated. DO NOT * CALL THIS METHOD DIRECTLY, but instead call {@linkplain Products#getAllProducts()} * which causes the ({@linkplain #_products}) field to be properly initialized. * * @return an array of all products (not <code>null</code>, contains no * <code>null</code>s) */ public static IProduct[] getAllProducts() { return new IProduct[] {getInstance()}; } /** * Answer the internal base identifier for this product. * * @return the identifier (not <code>null</code>, not empty) */ public String getProductId() { return PRODUCT_ID; } public String[] getPluginIds() { return PLUGIN_IDS; } public String getName() { return NAME; } public String getDescription() { return DESCRIPTION; } /** * Determine if the specified plugin is installed in the currently executing development * environment without actually loading or starting the plugin * * @param pluginId the plugin identifier * @return <code>true</code> if installed, else <code>false</code> */ public static boolean isInstalled(String pluginId) { return PluginUtilities.getInstallUrl(pluginId) != null; } public PluginVersionIdentifier getVersion() { if (version != null) return version; // Determine if the platform is running boolean isEclipseRunning; /* $codepro.preprocessor.if version >= 3.0 $ */ try { isEclipseRunning = Platform.isRunning(); } catch (NoClassDefFoundError e) { System.out.println(e); version = IProduct.UNKNOWN_VERSION; return version; } /* $codepro.preprocessor.elseif version < 3.0 $ isEclipseRunning = true; $codepro.preprocessor.endif $ */ // If Eclipse is running, get the version from the plugin if (isEclipseRunning) { version = getPluginVersion(getPluginId()); return version; } // If Eclipse is NOT running, get the version from the ProductInfo if (!isInstalled()) { version = IProduct.UNKNOWN_VERSION; return version; } String infoClassName = getProductInfoClassName(); try { Class infoClass = getClass().getClassLoader().loadClass(infoClassName); Field versionField = infoClass.getField("version"); String versionString = (String) versionField.get(null); version = new PluginVersionIdentifier(versionString); } catch (Exception e) { Logger.log(getName(), e); // Failed to get version of version = IProduct.UNKNOWN_VERSION; } return version; } /** * Answer the version for the specified plugin * * @param id the unique plugin identifier (not <code>null</code>) * @return the version for the plugin or 0.0.0 if it could not be determined */ public static PluginVersionIdentifier getPluginVersion(String pluginId) { PluginVersionIdentifier version = null; Throwable exception = null; try { version = PluginUtilities.getVersion(pluginId); } catch (Exception e) { exception = e; } if (version == null) { // DDC 9/22/2010 Disabled this output. //Logger.log(LicenseUtil // .decode("XUPSVRWPMMHJDDWEPC777866K422D06Y0W3USSMM5NKKXHWFHDNBJ944F543YYAZ4XBVVTVRYPHH") // + pluginId, exception); // Failed to determine version of plugin version = IProduct.UNKNOWN_VERSION; } return version; } public String getBuild() { if (build != null) return build; // Determine if the platform is running boolean isEclipseRunning; /* $codepro.preprocessor.if version >= 3.0 $ */ try { isEclipseRunning = Platform.isRunning(); } catch (NoClassDefFoundError e) { System.out.println(e); build = IProduct.UNKNOWN_BUILD; return build; } /* $codepro.preprocessor.elseif version < 3.0 $ isEclipseRunning = true; $codepro.preprocessor.endif $ */ // If Eclipse is running, get the build from the plugin if (isEclipseRunning) { build = getPluginBuild(getPluginId()); return build; } // If Eclipse is NOT running, get the build from the ProductInfo if (!isInstalled()) { build = IProduct.UNKNOWN_BUILD; return build; } String infoClassName = getProductInfoClassName(); try { Class infoClass = getClass().getClassLoader().loadClass(infoClassName); Field buildField = infoClass.getField(BUILD_KEY); build = (String) buildField.get(null); if (build == null) build = IProduct.UNKNOWN_BUILD; } catch (Exception e) { Logger.log(getName(), e); // Failed to determine build for build = IProduct.UNKNOWN_BUILD; } return build; } /** * If this product is to be used outside Eclipse, * override this method to return the fully qualified name for a class * that has a public static final field named "build" containing the build number (e.g. "200905111215") * and a public static final field named "version" containing the version (e.g. "3.9.0") * The default implementation returns <code>null</code> indicating * that the product is not licensed for use outside Eclipse. */ protected String getProductInfoClassName() { return null; } /** * Concrete implementation of {@link IProduct#getPluginId()} that iterates * through all identifiers returned by {@link #getPluginIds()} * and returns the first plugin with that identifier that is installed. * If no plugin is found, then the first identifier is returned. * */ public final String getPluginId() { String[] allIds = getPluginIds(); for (int i = 0; i < allIds.length; i++) { if (isInstalled(allIds[i])) return allIds[i]; } return allIds[0]; } /** * Concrete implementation of {@link IProduct#isInstalled()} that iterates * through all identifiers returned by {@link #getPluginIds()} and returns * <code>true</code> if it finds a plugin with that identifier that is * installed. If no plugin is found, then returns <code>false</code>. * * If executing outside Eclipse, then the existence of the ProductInfo class * as returned by {@link #getProductInfoClassName()} is used to determine * if the product is installed * * */ public final boolean isInstalled() { // Determine if the platform is running boolean isEclipseRunning; /* $codepro.preprocessor.if version >= 3.0 $ */ try { isEclipseRunning = Platform.isRunning(); } catch (NoClassDefFoundError e) { System.out.println(e); return false; } /* $codepro.preprocessor.elseif version < 3.0 $ isEclipseRunning = true; $codepro.preprocessor.endif $ */ // If Eclipse is running, get the build from the plugin if (isEclipseRunning) { String[] allIds = getPluginIds(); for (int i = 0; i < allIds.length; i++) { if (isInstalled(allIds[i])) return true; } return false; } // If Eclipse is NOT running, get the build from the ProductInfo String infoClassName = getProductInfoClassName(); // System.out.println("[" + getName() + " getProductInfoClassName() returned " + infoClassName + "]"); if (infoClassName == null) return false; try { Class infoClass = getClass().getClassLoader().loadClass(infoClassName); return infoClass != null; } catch (Exception e) { Logger.log(infoClassName + getName(), e); // Failed to find class // for return false; } } /** * Answer the build for the specified plugin. * This method ASSUMES that we are executing inside an Eclipse based application. * * @param id the unique plugin identifier (not <code>null</code>) * @return the build for the plugin or UNKNOWN if it could not be determined */ public static String getPluginBuild(String pluginId) { URL url = PluginUtilities.getUrl(pluginId, PLUGIN_PROPERTIES); if (url != null) { Properties properties = new Properties(); InputStream stream = null; try { stream = url.openStream(); properties.load(stream); } catch (IOException e) { Logger.log(e); } finally { try { if (stream != null) stream.close(); } catch (Exception e) { Logger.log(e); } } String build = properties.getProperty(BUILD_KEY); // If this is a code under development in a runtime workbench, then return today's date if (build == null || build.equals(DEVELOPMENT_BUILD_NUM)) return Products.getStartDateTimeString(); if (build.length() > 0) return build; } return IProduct.UNKNOWN_BUILD; } /** * Convert the build ({@link #getBuild()}) into a date and answer the result. * * @return the build date or <code>null</code> if it cannot be translated */ public GregorianCalendar getBuildDate() { if (buildDate == null) buildDate = getBuildDate(getBuild()); return buildDate; } /** * Convert the build ({@link #getBuild()}) into a date and answer the result. * * @param build the build to be converted into a date * @return the build date or <code>null</code> if it cannot be translated */ public static GregorianCalendar getBuildDate(String build) { // If this is a code under development in a runtime workbench, then return today's date if (build == null || build.equals(DEVELOPMENT_BUILD_NUM)) return new GregorianCalendar(); int len = build.length(); if (len < 8) return null; for (int i = 0; i < 8; i++) if (!Character.isDigit(build.charAt(i))) return null; try { int year = Integer.parseInt(build.substring(0, 4)); int month = Integer.parseInt(build.substring(4, 6)); int day = Integer.parseInt(build.substring(6, 8)); return new GregorianCalendar(year, month - 1, day); } catch (NumberFormatException e) { return null; } } //////////////////////////////////////////////////////////////////////////// // // Product Compatibility Checking // //////////////////////////////////////////////////////////////////////////// /** * Check compatibility between this product and the version of the Eclipse IDE in * which it is executing. Subclasses may override to provide additional behavior such * as checking the Eclipse build date. * * @return <code>true</code> if this product is compatible, else <code>false</code> */ public boolean isCompatibleWithIDE() { PluginVersionIdentifier actual = PlatformInfo.getEclipseVersion(); PluginVersionIdentifier expected = getExpectedEclipseVersion(); /* $codepro.preprocessor.if version > 4.2 $ This preprocessor code exists to remind us to adjust the following things once we start compiling against Eclipse 4.0. * adjust this preprocessor code to be version > 4.0 * adjust the expression below to allow 3.6 compiled code to execute on 4.0 and above * delete all warn_if_E35_running_on_E36 methods assuming all products compile against 3.3 * create a new warn_if_E35_running_on_E40 method * adjust the installer to recognize Eclipse 40 as a valid target for installation * anything else ? ... please list it here ... Please talk with Dan when making these adjustments $codepro.preprocessor.endif $ */ /* * If this product is NOT compiled against Eclipse 3.7, then do not warn the user * when running code compiled for Eclipse 3.5 on Eclipse 3.6. Individual products * should override #warn_if_E34_running_on_E35 as appropriate. */ if (!warn_if_E37_running_on_E38_or_42() && ((actual.getMajorComponent() == 4 && actual.getMinorComponent() == 2) || (actual.getMajorComponent() == 3 && actual.getMinorComponent() == 8)) && expected.getMajorComponent() == 3 && expected.getMinorComponent() == 7) return true; // First check major and minor version numbers if (actual.getMajorComponent() != expected.getMajorComponent() || actual.getMinorComponent() != expected.getMinorComponent()) return false; return true; } /** * Should the user be warned if the Eclipse 3.4 version of this product is running on * Eclipse 3.6 or 4.0? Subclasses should override this method and return <code>true</code> * if a version of this product exists that is compiled against Eclipse 3.4. * * @return <code>true</code> the user should be warned, else false. */ protected boolean warn_if_E37_running_on_E38_or_42() { // Our goal is to have all products including D2 building on Hudson and for E-3.8 and E-4.2 by April 1st return new GregorianCalendar().after(new GregorianCalendar(2012, 4, 1)); } /** * Print a message detailing the incompatibility between the currently installed * product and the IDE in which it is currently executing. Typically, * {@link #isCompatibleWithIDE()} is called to determine if this method should be * called. Subclasses may override to change the generated message. * * @param writer the writer to which the incompatibility message is appended */ public void printIDECompatibilityWarningMessage(PrintWriter writer) { PluginVersionIdentifier actual = PlatformInfo.getEclipseVersion(); writer.print("This version of "); writer.print(getName()); writer.print(" was compiled for "); writer.print(getExpectedEclipseText()); writer.print(" but is running on Eclipse "); writer.print(actual.getMajorComponent()); writer.print("."); writer.print(actual.getMinorComponent()); String buildName = PlatformInfo.getEclipseBuildName(); if (buildName != null && buildName.length() > 0) { writer.print(" "); writer.print(buildName); } writer.println("."); } /** * Answer the version of Eclipse for which this product was built * * @return the expected Eclipse version or {@link #UNKNOWN_VERSION} if it cannot be * determined (not <code>null</code>) */ protected PluginVersionIdentifier getExpectedEclipseVersion() { if (expectedEclipseVersion == null) expectedEclipseVersion = getExpectedEclipseVersion(getPluginId()); return expectedEclipseVersion; } /** * Answer the version of Eclipse for which the specfied plug-in was built * * @param pluginId the unique identifier for the plug-in * @return the expected Eclipse version or {@link #UNKNOWN_VERSION} if it cannot be * determined (not <code>null</code>) */ protected static PluginVersionIdentifier getExpectedEclipseVersion(String pluginId) { URL url = PluginUtilities.getUrl(pluginId, TARGET_PROPERTIES); // If this is the runtime workbench then the target.properties file does not exist // so simply return the current Eclipse version if (url == null) return PlatformInfo.getEclipseVersion(); Properties props = new Properties(); InputStream stream; try { stream = url.openStream(); } catch (IOException e) { Logger.log("Failed to open stream " + url, e); return UNKNOWN_VERSION; } try { props.load(stream); } catch (IOException e) { Logger.log("Failed to read stream " + url, e); return UNKNOWN_VERSION; } finally { try { stream.close(); } catch (IOException e) { Logger.log("Failed to close stream " + url, e); } } String version = (String) props.get(ECLIPSE_VERSION_KEY); if (version == null || version.length() == 0) { Logger.log("Failed to find " + ECLIPSE_VERSION_KEY + " in " + url); return UNKNOWN_VERSION; } try { return new PluginVersionIdentifier(version); } catch (Exception e) { Logger.log("Failed to parse " + ECLIPSE_VERSION_KEY + "=" + version + " in " + url, e); return UNKNOWN_VERSION; } } /** * Answer the version of Eclipse for which this product is compiled. * * @return the version (not <code>null</code>) */ public String getExpectedEclipseText() { StringBuffer buf = new StringBuffer(10); buf.append("Eclipse "); PluginVersionIdentifier expected = getExpectedEclipseVersion(); buf.append(expected.getMajorComponent()); buf.append("."); buf.append(expected.getMinorComponent()); // buf.append("."); // buf.append(expected.getServiceComponent()); // Adjust this for each milestone and release candidate // if (expected.getMajorComponent() == 3 && expected.getMinorComponent() == 3) // buf.append(" M1"); return buf.toString(); } //////////////////////////////////////////////////////////////////////////// // // Debugging // //////////////////////////////////////////////////////////////////////////// public String toString() { return getName(); } }