package com.mobilesorcery.sdk.profiles; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.eclipse.core.runtime.IPath; import org.eclipse.jface.resource.ImageDescriptor; import org.xml.sax.Attributes; import com.mobilesorcery.sdk.core.Capabilities; import com.mobilesorcery.sdk.core.Capability; import com.mobilesorcery.sdk.core.CapabilityFragmentation; import com.mobilesorcery.sdk.core.CapabilityState; import com.mobilesorcery.sdk.core.CommandLineExecutor; import com.mobilesorcery.sdk.core.CoreMoSyncPlugin; import com.mobilesorcery.sdk.core.ICapabilities; import com.mobilesorcery.sdk.core.LineReader; import com.mobilesorcery.sdk.core.MoSyncTool; import com.mobilesorcery.sdk.core.ProfileManager; import com.mobilesorcery.sdk.core.Util; import com.mobilesorcery.sdk.profiles.filter.DeviceCapabilitiesFilter; public class ProfileDBManager extends ProfileManager { public class ProfileDBResult { public final HashMap<String, Vendor> families = new HashMap<String, Vendor>(); public final Set<IProfile> profiles = new HashSet<IProfile>(); public final Set<String> capabilities = new TreeSet<String>(); public final HashSet<String> permissions = new HashSet<String>(); public final HashMap<String, List<IProfile>> profilesForRuntime = new HashMap<String, List<IProfile>>(); public final HashMap<String, String> profileMappings = new HashMap<String, String>(); public final HashMap<IProfile, Capabilities> capabilitiesForProfiles = new HashMap<IProfile, Capabilities>(); } private static final class ProfileDBLineHandler extends LineReader.XMLLineAdapter { private final CountDownLatch done; private boolean inMapTag = false; private final ProfileDBResult result; private Profile currentProfile; public ProfileDBLineHandler(CountDownLatch done, ProfileDBResult result) { this.done = done; this.result = result; } @Override public void startElement(String uri, String localName, String qName, Attributes atts) { if ("platform".equals(qName)) { String familyName = atts.getValue("family"); String variant = atts.getValue("variant"); String runtime = atts.getValue("runtime"); Vendor family = result.families.get(familyName); if (family == null) { IPath iconDir = MoSyncTool.getDefault().getProfilesPath() .append("platforms").append(familyName); ImageDescriptor icon = LegacyProfileManager .getIconForVendor(iconDir.toFile()); family = new Vendor(familyName, icon); result.families.put(familyName, family); } currentProfile = new Profile(family, variant, MoSyncTool.DEFAULT_PROFILE_TYPE); setRuntime(currentProfile, runtime); if (!inMapTag) { // We never actually add mapped tags // since they are only used for finding // platforms for devices. family.addProfile(currentProfile); result.profiles.add(currentProfile); } else { inMapTag = false; } } else if ("capability".equals(qName)) { String name = atts.getValue("name"); String type = atts.getValue("type"); boolean isNested = name.contains("/"); Object value = atts.getValue("value"); if (value == null) { value = Boolean.TRUE; } CapabilityState state = CapabilityState.create(atts .getValue("state")); CapabilityFragmentation fragmentation = CapabilityFragmentation .create(atts.getValue("fragmentation")); if (!isNested) { // TODO: Nested capabilities! Capability cap = new Capability(name, state, type, value, fragmentation); if (Util.isEmpty(type)) { // Ok, this is the list of available caps! result.capabilities.add(name); } else if ("property".equals(type) && currentProfile != null) { currentProfile.getModifiableProperties().put(name, value); } if (currentProfile != null) { Capabilities caps = result.capabilitiesForProfiles.get(currentProfile); if (caps == null) { caps = new Capabilities(); result.capabilitiesForProfiles.put(currentProfile, caps); } caps.setCapability(cap); } } if (state == CapabilityState.REQUIRES_PERMISSION || state == CapabilityState.REQUIRES_PRIVILEGED_PERMISSION) { result.permissions.add(name); } } else if ("map".equals(qName)) { String profileName = atts.getValue("platform"); String ref = atts.getValue("ref"); if (profileName != null && ref != null) { result.profileMappings.put(profileName, ref); } inMapTag = true; } } private void setRuntime(Profile profile, String runtime) { // For backwards compatibility, add platforms/runtime... profile.setRuntime("profiles/runtimes/" + runtime); String canonicalRuntime = toCanonicalRuntime(profile.getRuntime()); List<IProfile> profilesForThisRuntime = result.profilesForRuntime .get(canonicalRuntime); if (profilesForThisRuntime == null) { profilesForThisRuntime = new ArrayList<IProfile>(); result.profilesForRuntime.put(canonicalRuntime, profilesForThisRuntime); } profilesForThisRuntime.add(profile); } @Override public synchronized void doStop(Exception e) { if (done != null) { done.countDown(); } } } private static ProfileDBManager instance = new ProfileDBManager(); private final TreeMap<String, Vendor> vendors = new TreeMap<String, Vendor>( String.CASE_INSENSITIVE_ORDER); private final TreeSet<String> capabilities = new TreeSet<String>( String.CASE_INSENSITIVE_ORDER); private final TreeSet<String> permissions = new TreeSet<String>( String.CASE_INSENSITIVE_ORDER); private HashMap<String, List<IProfile>> profilesForRuntime = new HashMap<String, List<IProfile>>(); private boolean inited = false; private HashMap<IProfile, Capabilities> capabilitiesForProfiles; public static ProfileDBManager getInstance() { return instance; } @Override public synchronized void init() { if (inited) { return; } inited = true; ProfileDBLineHandler lh = runProfileDb(new ArrayList<String>( Arrays.asList(new String[] { "-g", "+" }))); vendors.clear(); vendors.putAll(lh.result.families); capabilities.addAll(lh.result.capabilities); permissions.addAll(lh.result.permissions); capabilitiesForProfiles = lh.result.capabilitiesForProfiles; // Ok, give us some mappings! ProfileDBResult matchResult = match("+", new String[0], new String[0]); profilesForRuntime = matchResult.profilesForRuntime; } public static boolean isAvailable() { return getTool().toFile().exists(); } private static IPath getTool() { IPath profiledb = MoSyncTool.getDefault().getBinary("profiledb"); return profiledb; } private ProfileDBLineHandler runProfileDb(List<String> args) { CountDownLatch done = new CountDownLatch(1); IPath profiledb = getTool(); CommandLineExecutor profileDbExe = new CommandLineExecutor( CoreMoSyncPlugin.LOG_CONSOLE_NAME); ProfileDBLineHandler lh = new ProfileDBLineHandler(done, new ProfileDBResult()); profileDbExe.setLineHandlers(lh, null); try { ArrayList<String> cmdLine = new ArrayList<String>(); cmdLine.add(profiledb.toOSString()); cmdLine.addAll(args); profileDbExe.runCommandLine(cmdLine.toArray(new String[0])); } catch (Exception e) { // Then validate will kick in and notify the user! CoreMoSyncPlugin.getDefault().log(e); } try { done.await(5, TimeUnit.SECONDS); } catch (InterruptedException e) { // Ignore. Thread.currentThread().interrupt(); } return lh; } @Override public IVendor[] getVendors() { init(); return vendors.values().toArray(new IVendor[0]); } @Override public IVendor getVendor(String vendorName) { init(); return vendors.get(vendorName); } @Override public String[] getAvailableCapabilities(boolean permissionsOnly) { init(); if (permissionsOnly) { return permissions.toArray(new String[0]); } else { return capabilities.toArray(new String[0]); } } /** * <p> * Tries to match profiles and returns the matched profiles and a mapping of * profiles, where every key of the map is a matching profile, but the * corresponding value which profile will be actually used. * </p> * <p> * For example, for some projects Android/1.5 will match as well as * Android/2.3. Then the preferred platform variant would be Android/1.5, * but the map returned by this method will allow us to see that instead of * Android/2.3 we will use Android/1.5. * * @param profilePattern * @param requiredCapabilities * @param optionalCapabilities * @return */ public ProfileDBResult match(String profilePattern, String[] requiredCapabilities, String[] optionalCapabilities) { init(); ArrayList<String> args = new ArrayList<String>(); args.add("--list-mappings"); args.add("--no-caps"); args.add("-m"); args.add(profilePattern); args.addAll(Arrays.asList(requiredCapabilities)); if (optionalCapabilities.length > 0) { args.add("-o"); args.addAll(Arrays.asList(optionalCapabilities)); } ProfileDBLineHandler lh = runProfileDb(args); return lh.result; } @Override public List<IProfile> getProfilesForRuntime(String runtime) { init(); List<IProfile> result = profilesForRuntime .get(toCanonicalRuntime(runtime)); return result == null ? null : Collections.unmodifiableList(result); } public ICapabilities getCapabilities(IProfile profile) { return capabilitiesForProfiles.get(profile); } public static String getPlatform(IProfile profile) { if (profile.getProfileType() == MoSyncTool.DEFAULT_PROFILE_TYPE) { return profile.getVendor().getName(); } else { IProfile platformProfile = DeviceCapabilitiesFilter.matchLegacyProfile(profile); if (platformProfile != null) { return platformProfile.getVendor().getName(); } else { return null; } } } }