/* Copyright (C) 2009 Mobile Sorcery AB This program is free software; you can redistribute it and/or modify it under the terms of the Eclipse Public License v1.0. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Eclipse Public License v1.0 for more details. You should have received a copy of the Eclipse Public License v1.0 along with this program. It is also available at http://www.eclipse.org/legal/epl-v10.html */ package com.mobilesorcery.sdk.core; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.LineNumberReader; import java.io.OutputStream; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.jface.util.IPropertyChangeListener; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.mobilesorcery.sdk.profiles.IProfile; import com.mobilesorcery.sdk.profiles.IVendor; import com.mobilesorcery.sdk.profiles.LegacyProfileManager; import com.mobilesorcery.sdk.profiles.ProfileDBManager; import com.mobilesorcery.sdk.profiles.ProfileParser; import com.sun.org.apache.xml.internal.serialize.OutputFormat; import com.sun.org.apache.xml.internal.serialize.XMLSerializer; public class MoSyncTool { public final static String CONSTANT_PREFIX = "MA_PROF_CONST_"; public static final String UNVERSIONED = ""; /** * A filter for excluding constants from the constants. */ public final static Filter<String> INCLUDE_CONSTANTS_FILTER = new Filter<String>() { @Override public boolean accept(String obj) { return obj != null && obj.startsWith(CONSTANT_PREFIX); } }; public final static String MOSYNC_HOME_PREF = "mosync.home"; public static final String MO_SYNC_HOME_FROM_ENV_PREF = "mosync.home.env"; public static final String AUTO_UPDATE_PREF = "mosync.auto.update"; public static final Filter<String> EXCLUDE_CONSTANTS_FILTER = INCLUDE_CONSTANTS_FILTER .inverse(); private static final String MOSYNC_ENV_VAR = "MOSYNCDIR"; /** * @deprecated This property is obsolete since the 'new' update/registration * process */ @Deprecated public static final String USER_HASH_PROP = "hash"; /** * The key used by the 'new' update process. (We really must find a better * name than 'new'...) Also, we do not reuse the old key. */ public static final String USER_HASH_PROP_2 = "user.key"; public static final String EMAIL_PROP = "email"; public static final String PROFILES_UPDATED = "profiles.updated"; public static final String MOSYNC_HOME_UPDATED = "mosync.home.updated"; public static final int LEGACY_PROFILE_TYPE = 1; public static final int DEFAULT_PROFILE_TYPE = 0; public static final int BINARY_VERSION = 0; public static final int BUILD_DATE = 1; public static final int MOSYNC_GIT_HASH = 2; public static final int ECLIPSE_GIT_HASH = 3; private static MoSyncTool instance = new MoSyncTool(true); private boolean inited = false; private Map<String, String> featureDescriptions = new TreeMap<String, String>(); /** * This is a reverse map: feature descriptions -> feature ids. (This is * because of 'old' project format storing the descriptions instead of the * id's. */ private final Map<String, String> featureDescriptionToIdMap = new TreeMap<String, String>(); private TreeMap<String, String> properties = new TreeMap<String, String>(); private final PropertyChangeSupport listeners = new PropertyChangeSupport( this); private IPath overrideHome; private LegacyProfileManager legacyProfileManager; private ProfileDBManager defaultProfileManager; private int profileManagerType; private String[] versionInfo; private MoSyncTool(boolean initListeners) { if (initListeners) { addPreferenceStoreListeners(); } } private static MoSyncTool createMoSyncTool(IPath overrideHome) { MoSyncTool tool = new MoSyncTool(false); tool.overrideHome = overrideHome; return tool; } private void addPreferenceStoreListeners() { if (CoreMoSyncPlugin.getDefault() != null) { CoreMoSyncPlugin.getDefault().getPreferenceStore() .addPropertyChangeListener(new IPropertyChangeListener() { @Override public void propertyChange( org.eclipse.jface.util.PropertyChangeEvent event) { reinit(); listeners .firePropertyChange(new PropertyChangeEvent( this, MOSYNC_HOME_UPDATED, event .getOldValue(), event .getNewValue())); } }); } } public static MoSyncTool getDefault() { return instance; } public IPath getMoSyncHome() { if (overrideHome != null) { return overrideHome; } else { return getMoSyncHomeFromEnv(); } } /** * Determines whether a specified home directory constitutes a proper mosync * home directory * * @param home * @return */ public static boolean isValidHome(IPath home) { MoSyncTool guess = createMoSyncTool(home); return guess.isValid(); } /** * Returns the default home directory as described in the system environment * variable <code>MOSYNCDIR</code>. * * @return An empty path if no <code>MOSYNCDIR</code> environment variable * is set. */ public static IPath getMoSyncHomeFromEnv() { String env = System.getenv(MOSYNC_ENV_VAR); if (env != null) { return new Path(env); } return new Path(""); } /** * Returns the <code>bin</code> directory, where all binaries are located. * * @return */ public IPath getMoSyncBin() { return getMoSyncHome().append("bin"); } /** * Returns the <code>lib</code> directory, where all binary libs are * located. * * @return */ public IPath getMoSyncLib() { return getMoSyncHome().append("lib"); } /** * Returns the extension directory */ public IPath getMoSyncExtensions() { return getMoSyncHome().append("modules"); } /** * Returns the <code>examples</code> directory, where all MoSync example * projects are stored. * * @return */ public IPath getMoSyncExamplesDirectory() { return getMoSyncHome().append("examples"); } /** * Returns the directory of the example workspace. * * @return */ public IPath getMoSyncExamplesWorkspace() { return getMoSyncExamplesDirectory().append("workspace"); } public IPath[] getMoSyncDefaultIncludes(boolean isNativeOutput) { return isNativeOutput ? new IPath[] { getMoSyncHome().append("include").append("MAStdNative") } : new IPath[] { getMoSyncHome().append("include") }; } public IPath[] getMoSyncDefaultLibraryPaths() { return new IPath[] { getMoSyncLib().append("pipe") }; } public IPath getProfilesPath() { return getMoSyncHome().append("profiles"); } public ProfileManager getProfileManager(int type) { switch (type) { case DEFAULT_PROFILE_TYPE: return defaultProfileManager(); case LEGACY_PROFILE_TYPE: return legacyProfileManager(); default: throw new IllegalArgumentException(); } } private synchronized LegacyProfileManager legacyProfileManager() { if (legacyProfileManager == null) { legacyProfileManager = new LegacyProfileManager(); legacyProfileManager.init(); } return legacyProfileManager; } private synchronized ProfileDBManager defaultProfileManager() { if (defaultProfileManager == null) { defaultProfileManager = ProfileDBManager.getInstance(); defaultProfileManager.init(); } return defaultProfileManager; } private IVendor[] getVendors() { return getProfileManager(profileManagerType).getVendors(); } public void reinit() { if (inited) { saveProperties(); } inited = false; init(); } private synchronized void init() { if (!inited) { try { if (!isValid()) { return; } initFeatureDescriptions(); initProperties(); profileManagerType = ProfileDBManager.isAvailable() ? DEFAULT_PROFILE_TYPE : LEGACY_PROFILE_TYPE; } finally { inited = true; listeners.firePropertyChange(PROFILES_UPDATED, null, this); } } } // Inits the properties of this tool, using the existing property structure private void initProperties() { try { properties = new TreeMap<String, String>(); Document doc = getPropertiesXMLDoc(); NodeList propertyChildren = doc.getElementsByTagName("Property"); for (int i = 0; i < propertyChildren.getLength(); i++) { Node child = propertyChildren.item(i); if (child instanceof Element) { Element element = (Element) child; String name = element.getAttribute("name"); String value = element.getAttribute("value"); if (name != null && value != null) { properties.put(name, value); } } } } catch (Exception e) { e.printStackTrace(); // Add to some state var! } } private void saveProperties() { if (!isValid()) { return; } OutputStream output = null; try { Document doc = getPropertiesXMLDoc(); NodeList configNode = doc.getElementsByTagName("Config"); Element configElement = null; if (configNode.getLength() > 0) { configElement = (Element) configNode.item(0); } else { configElement = doc.createElement("Config"); } NodeList children = configElement.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (child instanceof Element) { Element element = (Element) child; if (element.getTagName().equals("Property")) { configElement.removeChild(child); } } } for (Map.Entry<String, String> property : properties.entrySet()) { Element propertyElement = (Element) configElement .appendChild(doc.createElement("Property")); propertyElement.setAttribute("name", property.getKey()); propertyElement.setAttribute("value", property.getValue()); } output = new FileOutputStream(getPropertiesFile().toFile()); OutputFormat format = new OutputFormat(); format.setLineSeparator("\n"); format.setIndent(2); format.setLineWidth(132); XMLSerializer serializer = new XMLSerializer(output, format); serializer.asDOMSerializer(); serializer.serialize(doc.getDocumentElement()); } catch (Exception e) { e.printStackTrace(); // Add to some state var! } finally { if (output != null) { try { output.close(); } catch (IOException e) { // Ignore. } } } } private Document getPropertiesXMLDoc() throws ParserConfigurationException, IOException { File propertiesFile = getPropertiesFile().toFile(); if (!propertiesFile.getParentFile().exists()) { propertiesFile.getParentFile().mkdirs(); } if (propertiesFile.exists()) { try { return DocumentBuilderFactory.newInstance() .newDocumentBuilder().parse(propertiesFile); } catch (SAXException e) { // Just ignore - we'll create a doc from scratch. } } Document doc = DocumentBuilderFactory.newInstance() .newDocumentBuilder().newDocument(); Element root = doc.createElement("Config"); doc.appendChild(root); return doc; } private IPath getPropertiesFile() { return getMoSyncHome().append("etc").append("config.xml"); } public IPath getVendorsPath() { IPath profiles = getProfilesPath(); return profiles.append("vendors"); } public IPath getVendorPath(IVendor vendor) { return getVendorsPath().append(vendor.getName()); } public IPath getProfilePath(IProfile profile) { return getVendorPath(profile.getVendor()).append(profile.getName()); } public IPath getProfileInfoFile(IProfile profile) { return getProfilePath(profile).append("maprofile.h"); } public IPath getTemplatesPath() { return getMoSyncHome().append("templates"); } private void initFeatureDescriptions() { if (!isValid()) { return; } IPath featureDescriptionsPath = getProfilesPath().append("vendors") .append("definitions.txt"); try { featureDescriptions = new ProfileParser() .parseFeatureDescriptionFile(featureDescriptionsPath .toFile()); } catch (IOException e) { CoreMoSyncPlugin .getDefault() .getLog() .log(new Status( IStatus.ERROR, CoreMoSyncPlugin.PLUGIN_ID, "Could not parse feature description file - this may affect device filtering", e)); } for (Iterator<Map.Entry<String, String>> featureDescriptionIterator = featureDescriptions .entrySet().iterator(); featureDescriptionIterator.hasNext();) { Map.Entry<String, String> entry = featureDescriptionIterator.next(); featureDescriptionToIdMap.put(entry.getValue(), entry.getKey()); } } public String getFeatureDescription(String featureId) { init(); return featureDescriptions.get(featureId); } public String getFeatureId(String featureDescription) { init(); return featureDescriptionToIdMap.get(featureDescription); } public String[] getAvailableFeatureDescriptions() { init(); return featureDescriptions.keySet().toArray(new String[0]); } public String[] getAvailableFeatureDescriptions(Filter<String> filter) { init(); return Filter.filterMap(featureDescriptions, filter).keySet() .toArray(new String[0]); } /** * Returns the default emulator profile. * * @return */ public IProfile getDefaultEmulatorProfile() { init(); IVendor defaultVendor = legacyProfileManager.getVendor("MoSync"); if (defaultVendor != null) { return defaultVendor.getProfile("Emulator"); } return null; } public IPath getRuntimePath(IProfile targetProfile) { return getRuntimePath(targetProfile.getRuntime()); } public IPath getRuntimePath(String platform) { init(); // The platform is always a /-separated path return getMoSyncHome().append(platform); } public String getProperty(String key) { init(); return properties.get(key); } public void setProperty(String key, String value) { init(); if (value == null) { properties.remove(key); } else { properties.put(key, value); } saveProperties(); } public void addPropertyChangeListener(PropertyChangeListener listener) { listeners.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { listeners.removePropertyChangeListener(listener); } /** * Validate this tool, and returns a proper error message if it's not * configured properly. * * @return */ public String validate() { if (getMoSyncHome() == null || getMoSyncHome().isEmpty()) { return "the MOSYNCDIR environment variable is not set properly"; } else if (!getMoSyncBin().toFile().exists()) { return "Invalid MoSync home - could not find bin directory"; } else if (!getProfilesPath().toFile().exists()) { return "Invalid MoSync home - could not find profiles directory"; } else if (!getVendorsPath().toFile().exists()) { return "Invalid MoSync home - could not find vendors directory"; } else if (inited && getProfileManager(DEFAULT_PROFILE_TYPE).getDefaultTargetProfile() == null) { // Please note that we need the inited check to avoid an infinite // loop. return "Tool in an incorrect state - no default target profile exists (so there seems to be something seriously strange with the directory structure of mosync - did you try to trick the IDE?)"; } return null; } public boolean isValid() { return validate() == null; } /** * Returns a path to the binary with the given name. If no such binary can * be found, null will be returned. * * @param name * Name of the binary, it should not contain the platform * specific extension, e.g. ".exe". * @return Path to the binary */ public IPath getBinary(String name) { String extension = getBinExtension(); return getMoSyncBin().append(name + extension); } /** * Returns the path to java binary defined by the java.home property. * * @return the path to java binary defined by the java.home property. */ public IPath getJava() { IPath javaHome = new Path(System.getProperty("java.home")); IPath javaBin = javaHome.append("bin/java" + getBinExtension()); return javaBin; } /** * Returns the extension of executables on the current platform, including * the period sign (.) if applicable. */ public static String getBinExtension() { String extension = ""; if (System.getProperty("os.name").toLowerCase().indexOf("win") >= 0) { extension = ".exe"; } return extension; } /** * Returns the version (build number) of the current set of MoSync binaries. * * @return */ public String getVersionInfo(int versionInfoType) { initVersionInfo(); if (versionInfoType < versionInfo.length && versionInfoType >= 0) { return versionInfo[versionInfoType]; } return ""; } private void initVersionInfo() { if (versionInfo == null) { versionInfo = new String[0]; File versionFile = MoSyncTool.getDefault().getMoSyncBin() .append("version.dat").toFile(); if (versionFile.exists()) { try { String fullVersionInfo = Util.readFile(versionFile.getAbsolutePath()); versionInfo = fullVersionInfo.split("\\s*\\r*\\n"); } catch (IOException e) { // Fallback } } } } private String getCurrentVersionFromFile(File versionFile) { if (versionFile.exists()) { try { return readVersion(versionFile); } catch (IOException e) { // Fallback } } return UNVERSIONED; } private String readVersion(File versionFile) throws IOException { FileReader input = new FileReader(versionFile); try { LineNumberReader lineInput = new LineNumberReader(input); String version = lineInput.readLine(); if (version != null) { return version; } } catch (Exception e) { // Fall-thru } finally { Util.safeClose(input); } return UNVERSIONED; } /** * Returns the registration key for this MoSync tool installation. * * @return */ public String getRegistrationKey() { // TODO Should be fetched from mosync web site String hash = getProperty(USER_HASH_PROP); if (hash != null) { return hash.substring(0, hash.length() / 2); } return "unregistered"; } /** * The 'inverse' of getProfile(fullName). * * @param preferredProfile * @return */ public static String toString(IProfile preferredProfile) { if (preferredProfile == null) { return ""; } else { return preferredProfile.getVendor() + "/" + preferredProfile.getName(); } } }