/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.eclipse.org/org/documents/epl-v10.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ide.eclipse.adt.internal.sdk; import static com.android.sdklib.SdkConstants.FD_DATA; import static com.android.sdklib.SdkConstants.FD_RES; import static com.android.sdklib.SdkConstants.FD_VALUES; import static java.io.File.separator; import com.android.ide.common.rendering.LayoutLibrary; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.sdk.LoadStatus; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors; import com.android.ide.eclipse.adt.internal.editors.menu.descriptors.MenuDescriptors; import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors; import com.android.ide.eclipse.adt.internal.editors.xml.descriptors.XmlDescriptors; import com.android.ide.eclipse.adt.internal.resources.IResourceRepository; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; import com.android.resources.ResourceType; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.IAndroidTarget.IOptionalLibrary; import org.eclipse.core.runtime.IStatus; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; /** * This class contains the data of an Android Target as loaded from the SDK. */ public class AndroidTargetData { public final static int DESCRIPTOR_MANIFEST = 1; public final static int DESCRIPTOR_LAYOUT = 2; public final static int DESCRIPTOR_MENU = 3; public final static int DESCRIPTOR_XML = 4; public final static int DESCRIPTOR_RESOURCES = 5; public final static int DESCRIPTOR_SEARCHABLE = 6; public final static int DESCRIPTOR_PREFERENCES = 7; public final static int DESCRIPTOR_APPWIDGET_PROVIDER = 8; private final IAndroidTarget mTarget; /** * mAttributeValues is a map { key => list [ values ] }. * The key for the map is "(element-xml-name,attribute-namespace:attribute-xml-local-name)". * The attribute namespace prefix must be: * - "android" for AndroidConstants.NS_RESOURCES * - "xmlns" for the XMLNS URI. * * This is used for attributes that do not have a unique name, but still need to be populated * with values in the UI. Uniquely named attributes have their values in {@link #mEnumValueMap}. */ private Hashtable<String, String[]> mAttributeValues = new Hashtable<String, String[]>(); private IResourceRepository mSystemResourceRepository; private AndroidManifestDescriptors mManifestDescriptors; private LayoutDescriptors mLayoutDescriptors; private MenuDescriptors mMenuDescriptors; private XmlDescriptors mXmlDescriptors; private Map<String, Map<String, Integer>> mEnumValueMap; private Map<ResourceType, Collection<String>> mPublicAttributeNames; private ProjectResources mFrameworkResources; private LayoutLibrary mLayoutLibrary; private boolean mLayoutBridgeInit = false; AndroidTargetData(IAndroidTarget androidTarget) { mTarget = androidTarget; } /** * Creates an AndroidTargetData object. * @param platformLibraries * @param optionalLibraries */ void setExtraData(IResourceRepository systemResourceRepository, AndroidManifestDescriptors manifestDescriptors, LayoutDescriptors layoutDescriptors, MenuDescriptors menuDescriptors, XmlDescriptors xmlDescriptors, Map<String, Map<String, Integer>> enumValueMap, String[] permissionValues, String[] activityIntentActionValues, String[] broadcastIntentActionValues, String[] serviceIntentActionValues, String[] intentCategoryValues, String[] platformLibraries, IOptionalLibrary[] optionalLibraries, ProjectResources resources, LayoutLibrary layoutLibrary) { mSystemResourceRepository = systemResourceRepository; mManifestDescriptors = manifestDescriptors; mLayoutDescriptors = layoutDescriptors; mMenuDescriptors = menuDescriptors; mXmlDescriptors = xmlDescriptors; mEnumValueMap = enumValueMap; mFrameworkResources = resources; mLayoutLibrary = layoutLibrary; setPermissions(permissionValues); setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues, serviceIntentActionValues, intentCategoryValues); setOptionalLibraries(platformLibraries, optionalLibraries); } public IResourceRepository getSystemResources() { return mSystemResourceRepository; } /** * Returns an {@link IDescriptorProvider} from a given Id. * The Id can be one of {@link #DESCRIPTOR_MANIFEST}, {@link #DESCRIPTOR_LAYOUT}, * {@link #DESCRIPTOR_MENU}, or {@link #DESCRIPTOR_XML}. * All other values will throw an {@link IllegalArgumentException}. */ public IDescriptorProvider getDescriptorProvider(int descriptorId) { switch (descriptorId) { case DESCRIPTOR_MANIFEST: return mManifestDescriptors; case DESCRIPTOR_LAYOUT: return mLayoutDescriptors; case DESCRIPTOR_MENU: return mMenuDescriptors; case DESCRIPTOR_XML: return mXmlDescriptors; case DESCRIPTOR_RESOURCES: // FIXME: since it's hard-coded the Resources Descriptors are not platform dependent. return ResourcesDescriptors.getInstance(); case DESCRIPTOR_PREFERENCES: return mXmlDescriptors.getPreferencesProvider(); case DESCRIPTOR_APPWIDGET_PROVIDER: return mXmlDescriptors.getAppWidgetProvider(); case DESCRIPTOR_SEARCHABLE: return mXmlDescriptors.getSearchableProvider(); default : throw new IllegalArgumentException(); } } /** * Returns the manifest descriptors. */ public AndroidManifestDescriptors getManifestDescriptors() { return mManifestDescriptors; } /** * Returns the layout Descriptors. */ public LayoutDescriptors getLayoutDescriptors() { return mLayoutDescriptors; } /** * Returns the menu descriptors. */ public MenuDescriptors getMenuDescriptors() { return mMenuDescriptors; } /** * Returns the XML descriptors */ public XmlDescriptors getXmlDescriptors() { return mXmlDescriptors; } /** * Returns this list of possible values for an XML attribute. * <p/>This should only be called for attributes for which possible values depend on the * parent element node. * <p/>For attributes that have the same values no matter the parent node, use * {@link #getEnumValueMap()}. * @param elementName the name of the element containing the attribute. * @param attributeName the name of the attribute * @return an array of String with the possible values, or <code>null</code> if no values were * found. */ public String[] getAttributeValues(String elementName, String attributeName) { String key = String.format("(%1$s,%2$s)", elementName, attributeName); //$NON-NLS-1$ return mAttributeValues.get(key); } /** * Returns this list of possible values for an XML attribute. * <p/>This should only be called for attributes for which possible values depend on the * parent and great-grand-parent element node. * <p/>The typical example of this is for the 'name' attribute under * activity/intent-filter/action * <p/>For attributes that have the same values no matter the parent node, use * {@link #getEnumValueMap()}. * @param elementName the name of the element containing the attribute. * @param attributeName the name of the attribute * @param greatGrandParentElementName the great-grand-parent node. * @return an array of String with the possible values, or <code>null</code> if no values were * found. */ public String[] getAttributeValues(String elementName, String attributeName, String greatGrandParentElementName) { if (greatGrandParentElementName != null) { String key = String.format("(%1$s,%2$s,%3$s)", //$NON-NLS-1$ greatGrandParentElementName, elementName, attributeName); String[] values = mAttributeValues.get(key); if (values != null) { return values; } } return getAttributeValues(elementName, attributeName); } /** * Returns the enum values map. * <p/>The map defines the possible values for XML attributes. The key is the attribute name * and the value is a map of (string, integer) in which the key (string) is the name of * the value, and the Integer is the numerical value in the compiled binary XML files. */ public Map<String, Map<String, Integer>> getEnumValueMap() { return mEnumValueMap; } /** * Returns the {@link ProjectResources} containing the Framework Resources. */ public ProjectResources getFrameworkResources() { return mFrameworkResources; } /** * Returns a {@link LayoutLibrary} object possibly containing a {@link LayoutBridge} object. * <p/>If {@link LayoutLibrary#getBridge()} is <code>null</code>, * {@link LayoutBridge#getStatus()} will contain the reason (either {@link LoadStatus#LOADING} * or {@link LoadStatus#FAILED}). * <p/>Valid {@link LayoutBridge} objects are always initialized before being returned. */ public synchronized LayoutLibrary getLayoutLibrary() { if (mLayoutBridgeInit == false && mLayoutLibrary.getStatus() == LoadStatus.LOADED) { boolean ok = mLayoutLibrary.init( mTarget.getProperties(), new File(mTarget.getPath(IAndroidTarget.FONTS)), getEnumValueMap(), new LayoutLog() { @Override public void error(String tag, String message, Throwable throwable, Object data) { AdtPlugin.log(throwable, message); } @Override public void error(String tag, String message, Object data) { AdtPlugin.log(IStatus.ERROR, message); } @Override public void warning(String tag, String message, Object data) { AdtPlugin.log(IStatus.WARNING, message); } }); if (!ok) { AdtPlugin.log(IStatus.ERROR, "LayoutLibrary initialization failed"); } mLayoutBridgeInit = true; } return mLayoutLibrary; } /** * Sets the permission values * @param permissionValues the list of permissions */ private void setPermissions(String[] permissionValues) { setValues("(uses-permission,android:name)", permissionValues); //$NON-NLS-1$ setValues("(application,android:permission)", permissionValues); //$NON-NLS-1$ setValues("(activity,android:permission)", permissionValues); //$NON-NLS-1$ setValues("(receiver,android:permission)", permissionValues); //$NON-NLS-1$ setValues("(service,android:permission)", permissionValues); //$NON-NLS-1$ setValues("(provider,android:permission)", permissionValues); //$NON-NLS-1$ } private void setIntentFilterActionsAndCategories(String[] activityIntentActions, String[] broadcastIntentActions, String[] serviceIntentActions, String[] intentCategoryValues) { setValues("(activity,action,android:name)", activityIntentActions); //$NON-NLS-1$ setValues("(receiver,action,android:name)", broadcastIntentActions); //$NON-NLS-1$ setValues("(service,action,android:name)", serviceIntentActions); //$NON-NLS-1$ setValues("(category,android:name)", intentCategoryValues); //$NON-NLS-1$ } private void setOptionalLibraries(String[] platformLibraries, IOptionalLibrary[] optionalLibraries) { ArrayList<String> libs = new ArrayList<String>(); if (platformLibraries != null) { for (String name : platformLibraries) { libs.add(name); } } if (optionalLibraries != null) { for (int i = 0; i < optionalLibraries.length; i++) { libs.add(optionalLibraries[i].getName()); } } setValues("(uses-library,android:name)", libs.toArray(new String[libs.size()])); } /** * Sets a (name, values) pair in the hash map. * <p/> * If the name is already present in the map, it is first removed. * @param name the name associated with the values. * @param values The values to add. */ private void setValues(String name, String[] values) { mAttributeValues.remove(name); mAttributeValues.put(name, values); } /** * Returns true if the given name represents a public attribute of the given type. * * @param type the type of resource * @param name the name of the resource * @return true if the given property is public */ public boolean isPublicResource(ResourceType type, String name) { Collection<String> names = getNameMap(type); if (names != null) { return names.contains(name); } return false; } /** * Returns all public properties (in no particular order) of a given resource type. * * @param type the type of resource * @return an unmodifiable collection of public resource names */ public Collection<String> getPublicResourceNames(ResourceType type) { Collection<String> names = getNameMap(type); if (names != null) { return Collections.<String>unmodifiableCollection(names); } return Collections.emptyList(); } /** Returns a (possibly cached) list of names for the given resource type, or null */ private Collection<String> getNameMap(ResourceType type) { if (mPublicAttributeNames == null) { mPublicAttributeNames = readPublicAttributeLists(); } return mPublicAttributeNames.get(type); } /** * Reads the public.xml file in data/res/values/ for this SDK and * returns the result as a map from resource type to a list of names */ private Map<ResourceType, Collection<String>> readPublicAttributeLists() { String relative = FD_DATA + separator + FD_RES + separator + FD_VALUES + separator + "public.xml"; //$NON-NLS-1$ File file = new File(mTarget.getLocation(), relative); if (file.isFile()) { Map<ResourceType, Collection<String>> map = new HashMap<ResourceType, Collection<String>>(); Document document = null; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); Reader reader = null; try { reader = new BufferedReader(new FileReader(file)); InputSource is = new InputSource(reader); factory.setNamespaceAware(true); factory.setValidating(false); DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.parse(is); ResourceType lastType = null; String lastTypeName = ""; NodeList children = document.getDocumentElement().getChildNodes(); for (int i = 0, n = children.getLength(); i < n; i++) { Node node = children.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { Element element = (Element) node; String name = element.getAttribute("name"); //$NON-NLS-1$ if (name.length() > 0) { String typeName = element.getAttribute("type"); //$NON-NLS-1$ ResourceType type = null; if (typeName.equals(lastTypeName)) { type = lastType; } else { type = ResourceType.getEnum(typeName); lastType = type; lastTypeName = typeName; } if (type != null) { Collection<String> list = map.get(type); if (list == null) { // Use sets for some of the larger maps to make // searching for isPublicResource faster. if (type == ResourceType.ATTR) { list = new HashSet<String>(900); } else if (type == ResourceType.STYLE) { list = new HashSet<String>(300); } else if (type == ResourceType.DRAWABLE) { list = new HashSet<String>(200); } else { list = new ArrayList<String>(30); } map.put(type, list); } list.add(name); } } } } } catch (Exception e) { AdtPlugin.log(e, "Can't read and parse public attribute list"); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { // Nothing to be done here - we don't care if it closed or not. } } } return map; } return Collections.emptyMap(); } }