blob: 9a1fd3dc981daf0afac037e504049963c09adcb6 [file] [log] [blame]
/*
* Copyright (C) 2008 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 com.android.SdkConstants;
import com.android.ide.common.rendering.LayoutLibrary;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.platform.AttrsXmlParser;
import com.android.ide.common.resources.platform.DeclareStyleableInfo;
import com.android.ide.common.resources.platform.ViewClassInfo;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.animator.AnimDescriptors;
import com.android.ide.eclipse.adt.internal.editors.animator.AnimatorDescriptors;
import com.android.ide.eclipse.adt.internal.editors.color.ColorDescriptors;
import com.android.ide.eclipse.adt.internal.editors.drawable.DrawableDescriptors;
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.otherxml.descriptors.OtherXmlDescriptors;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.management.InvalidAttributeValueException;
/**
* Parser for the platform data in an SDK.
* <p/>
* This gather the following information:
* <ul>
* <li>Resource ID from <code>android.R</code></li>
* <li>The list of permissions values from <code>android.Manifest$permission</code></li>
* <li></li>
* </ul>
*/
public final class AndroidTargetParser {
private static final String TAG = "Framework Resource Parser";
private final IAndroidTarget mAndroidTarget;
/**
* Creates a platform data parser.
*/
public AndroidTargetParser(IAndroidTarget platformTarget) {
mAndroidTarget = platformTarget;
}
/**
* Parses the framework, collects all interesting information and stores them in the
* {@link IAndroidTarget} given to the constructor.
*
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
* @return True if the SDK path was valid and parsing has been attempted.
*/
public IStatus run(IProgressMonitor monitor) {
try {
SubMonitor progress = SubMonitor.convert(monitor,
String.format("Parsing SDK %1$s", mAndroidTarget.getName()),
16);
AndroidTargetData targetData = new AndroidTargetData(mAndroidTarget);
// parse the rest of the data.
AndroidJarLoader classLoader =
new AndroidJarLoader(mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
preload(classLoader, progress.newChild(40, SubMonitor.SUPPRESS_NONE));
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
// get the permissions
progress.subTask("Permissions");
String[] permissionValues = collectPermissions(classLoader);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
// get the action and category values for the Intents.
progress.subTask("Intents");
ArrayList<String> activity_actions = new ArrayList<String>();
ArrayList<String> broadcast_actions = new ArrayList<String>();
ArrayList<String> service_actions = new ArrayList<String>();
ArrayList<String> categories = new ArrayList<String>();
collectIntentFilterActionsAndCategories(activity_actions, broadcast_actions,
service_actions, categories);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
// gather the attribute definition
progress.subTask("Attributes definitions");
AttrsXmlParser attrsXmlParser = new AttrsXmlParser(
mAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES),
AdtPlugin.getDefault(),
1000);
attrsXmlParser.preload();
progress.worked(1);
progress.subTask("Manifest definitions");
AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser(
mAndroidTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES),
attrsXmlParser,
AdtPlugin.getDefault(), 1100);
attrsManifestXmlParser.preload();
progress.worked(1);
Collection<ViewClassInfo> mainList = new ArrayList<ViewClassInfo>();
Collection<ViewClassInfo> groupList = new ArrayList<ViewClassInfo>();
// collect the layout/widgets classes
progress.subTask("Widgets and layouts");
collectLayoutClasses(classLoader, attrsXmlParser, mainList, groupList,
progress.newChild(1));
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
ViewClassInfo[] layoutViewsInfo = mainList.toArray(
new ViewClassInfo[mainList.size()]);
ViewClassInfo[] layoutGroupsInfo = groupList.toArray(
new ViewClassInfo[groupList.size()]);
mainList.clear();
groupList.clear();
// collect the preferences classes.
collectPreferenceClasses(classLoader, attrsXmlParser, mainList, groupList,
progress.newChild(1));
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
ViewClassInfo[] preferencesInfo = mainList.toArray(new ViewClassInfo[mainList.size()]);
ViewClassInfo[] preferenceGroupsInfo = groupList.toArray(
new ViewClassInfo[groupList.size()]);
Map<String, DeclareStyleableInfo> xmlMenuMap = collectMenuDefinitions(attrsXmlParser);
Map<String, DeclareStyleableInfo> xmlSearchableMap = collectSearchableDefinitions(
attrsXmlParser);
Map<String, DeclareStyleableInfo> manifestMap = collectManifestDefinitions(
attrsManifestXmlParser);
Map<String, Map<String, Integer>> enumValueMap = attrsXmlParser.getEnumFlagValues();
Map<String, DeclareStyleableInfo> xmlAppWidgetMap = null;
if (mAndroidTarget.getVersion().getApiLevel() >= 3) {
xmlAppWidgetMap = collectAppWidgetDefinitions(attrsXmlParser);
}
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
// From the information that was collected, create the pieces that will be put in
// the PlatformData object.
AndroidManifestDescriptors manifestDescriptors = new AndroidManifestDescriptors();
manifestDescriptors.updateDescriptors(manifestMap);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
LayoutDescriptors layoutDescriptors = new LayoutDescriptors();
layoutDescriptors.updateDescriptors(layoutViewsInfo, layoutGroupsInfo,
attrsXmlParser.getDeclareStyleableList(), mAndroidTarget);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
MenuDescriptors menuDescriptors = new MenuDescriptors();
menuDescriptors.updateDescriptors(xmlMenuMap);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
OtherXmlDescriptors otherXmlDescriptors = new OtherXmlDescriptors();
otherXmlDescriptors.updateDescriptors(
xmlSearchableMap,
xmlAppWidgetMap,
preferencesInfo,
preferenceGroupsInfo);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
DrawableDescriptors drawableDescriptors = new DrawableDescriptors();
Map<String, DeclareStyleableInfo> map = attrsXmlParser.getDeclareStyleableList();
drawableDescriptors.updateDescriptors(map);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
AnimatorDescriptors animatorDescriptors = new AnimatorDescriptors();
animatorDescriptors.updateDescriptors(map);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
AnimDescriptors animDescriptors = new AnimDescriptors();
animDescriptors.updateDescriptors(map);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
ColorDescriptors colorDescriptors = new ColorDescriptors();
colorDescriptors.updateDescriptors(map);
progress.worked(1);
// load the framework resources.
ResourceRepository frameworkResources =
ResourceManager.getInstance().loadFrameworkResources(mAndroidTarget);
progress.worked(1);
// now load the layout lib bridge
LayoutLibrary layoutBridge = LayoutLibrary.load(
mAndroidTarget.getPath(IAndroidTarget.LAYOUT_LIB),
AdtPlugin.getDefault(),
"ADT plug-in");
progress.worked(1);
// and finally create the PlatformData with all that we loaded.
targetData.setExtraData(
manifestDescriptors,
layoutDescriptors,
menuDescriptors,
otherXmlDescriptors,
drawableDescriptors,
animatorDescriptors,
animDescriptors,
colorDescriptors,
enumValueMap,
permissionValues,
activity_actions.toArray(new String[activity_actions.size()]),
broadcast_actions.toArray(new String[broadcast_actions.size()]),
service_actions.toArray(new String[service_actions.size()]),
categories.toArray(new String[categories.size()]),
mAndroidTarget.getPlatformLibraries(),
mAndroidTarget.getOptionalLibraries(),
frameworkResources,
layoutBridge);
targetData.setAttributeMap(attrsXmlParser.getAttributeMap());
Sdk.getCurrent().setTargetData(mAndroidTarget, targetData);
return Status.OK_STATUS;
} catch (Exception e) {
AdtPlugin.logAndPrintError(e, TAG, "SDK parser failed"); //$NON-NLS-1$
AdtPlugin.printToConsole("SDK parser failed", e.getMessage());
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "SDK parser failed", e);
}
}
/**
* Preloads all "interesting" classes from the framework SDK jar.
* <p/>
* Currently this preloads all classes from the framework jar
*
* @param classLoader The framework SDK jar classloader
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
*/
private void preload(AndroidJarLoader classLoader, IProgressMonitor monitor) {
try {
classLoader.preLoadClasses("" /* all classes */, //$NON-NLS-1$
mAndroidTarget.getName(), // monitor task label
monitor);
} catch (InvalidAttributeValueException e) {
AdtPlugin.log(e, "Problem preloading classes"); //$NON-NLS-1$
} catch (IOException e) {
AdtPlugin.log(e, "Problem preloading classes"); //$NON-NLS-1$
}
}
/**
* Loads, collects and returns the list of default permissions from the framework.
*
* @param classLoader The framework SDK jar classloader
* @return a non null (but possibly empty) array containing the permission values.
*/
private String[] collectPermissions(AndroidJarLoader classLoader) {
try {
Class<?> permissionClass =
classLoader.loadClass(SdkConstants.CLASS_MANIFEST_PERMISSION);
if (permissionClass != null) {
ArrayList<String> list = new ArrayList<String>();
Field[] fields = permissionClass.getFields();
for (Field f : fields) {
int modifiers = f.getModifiers();
if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers) &&
Modifier.isPublic(modifiers)) {
try {
Object value = f.get(null);
if (value instanceof String) {
list.add((String)value);
}
} catch (IllegalArgumentException e) {
// since we provide null this should not happen
} catch (IllegalAccessException e) {
// if the field is inaccessible we ignore it.
} catch (NullPointerException npe) {
// looks like this is not a static field. we can ignore.
} catch (ExceptionInInitializerError eiie) {
// lets just ignore the field again
}
}
}
return list.toArray(new String[list.size()]);
}
} catch (ClassNotFoundException e) {
AdtPlugin.logAndPrintError(e, TAG,
"Collect permissions failed, class %1$s not found in %2$s", //$NON-NLS-1$
SdkConstants.CLASS_MANIFEST_PERMISSION,
mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
}
return new String[0];
}
/**
* Loads and collects the action and category default values from the framework.
* The values are added to the <code>actions</code> and <code>categories</code> lists.
*
* @param activityActions the list which will receive the activity action values.
* @param broadcastActions the list which will receive the broadcast action values.
* @param serviceActions the list which will receive the service action values.
* @param categories the list which will receive the category values.
*/
private void collectIntentFilterActionsAndCategories(ArrayList<String> activityActions,
ArrayList<String> broadcastActions,
ArrayList<String> serviceActions, ArrayList<String> categories) {
collectValues(mAndroidTarget.getPath(IAndroidTarget.ACTIONS_ACTIVITY),
activityActions);
collectValues(mAndroidTarget.getPath(IAndroidTarget.ACTIONS_BROADCAST),
broadcastActions);
collectValues(mAndroidTarget.getPath(IAndroidTarget.ACTIONS_SERVICE),
serviceActions);
collectValues(mAndroidTarget.getPath(IAndroidTarget.CATEGORIES),
categories);
}
/**
* Collects values from a text file located in the SDK
* @param osFilePath The path to the text file.
* @param values the {@link ArrayList} to fill with the values.
*/
private void collectValues(String osFilePath, ArrayList<String> values) {
FileReader fr = null;
BufferedReader reader = null;
try {
fr = new FileReader(osFilePath);
reader = new BufferedReader(fr);
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.length() > 0 && line.startsWith("#") == false) { //$NON-NLS-1$
values.add(line);
}
}
} catch (IOException e) {
AdtPlugin.log(e, "Failed to read SDK values"); //$NON-NLS-1$
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
AdtPlugin.log(e, "Failed to read SDK values"); //$NON-NLS-1$
}
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
AdtPlugin.log(e, "Failed to read SDK values"); //$NON-NLS-1$
}
}
}
/**
* Collects all layout classes information from the class loader and the
* attrs.xml and sets the corresponding structures in the resource manager.
*
* @param classLoader The framework SDK jar classloader in case we cannot get the widget from
* the platform directly
* @param attrsXmlParser The parser of the attrs.xml file
* @param mainList the Collection to receive the main list of {@link ViewClassInfo}.
* @param groupList the Collection to receive the group list of {@link ViewClassInfo}.
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
*/
private void collectLayoutClasses(AndroidJarLoader classLoader,
AttrsXmlParser attrsXmlParser,
Collection<ViewClassInfo> mainList,
Collection<ViewClassInfo> groupList,
IProgressMonitor monitor) {
LayoutParamsParser ldp = null;
try {
WidgetClassLoader loader = new WidgetClassLoader(
mAndroidTarget.getPath(IAndroidTarget.WIDGETS));
if (loader.parseWidgetList(monitor)) {
ldp = new LayoutParamsParser(loader, attrsXmlParser);
}
// if the parsing failed, we'll use the old loader below.
} catch (FileNotFoundException e) {
AdtPlugin.log(e, "Android Framework Parser"); //$NON-NLS-1$
// the file does not exist, we'll use the old loader below.
}
if (ldp == null) {
ldp = new LayoutParamsParser(classLoader, attrsXmlParser);
}
ldp.parseLayoutClasses(monitor);
List<ViewClassInfo> views = ldp.getViews();
List<ViewClassInfo> groups = ldp.getGroups();
if (views != null && groups != null) {
mainList.addAll(views);
groupList.addAll(groups);
}
}
/**
* Collects all preferences definition information from the attrs.xml and
* sets the corresponding structures in the resource manager.
*
* @param classLoader The framework SDK jar classloader
* @param attrsXmlParser The parser of the attrs.xml file
* @param mainList the Collection to receive the main list of {@link ViewClassInfo}.
* @param groupList the Collection to receive the group list of {@link ViewClassInfo}.
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
*/
private void collectPreferenceClasses(AndroidJarLoader classLoader,
AttrsXmlParser attrsXmlParser, Collection<ViewClassInfo> mainList,
Collection<ViewClassInfo> groupList, IProgressMonitor monitor) {
LayoutParamsParser ldp = new LayoutParamsParser(classLoader, attrsXmlParser);
try {
ldp.parsePreferencesClasses(monitor);
List<ViewClassInfo> prefs = ldp.getViews();
List<ViewClassInfo> groups = ldp.getGroups();
if (prefs != null && groups != null) {
mainList.addAll(prefs);
groupList.addAll(groups);
}
} catch (NoClassDefFoundError e) {
AdtPlugin.logAndPrintError(e, TAG,
"Collect preferences failed, class %1$s not found in %2$s",
e.getMessage(),
classLoader.getSource());
} catch (Throwable e) {
AdtPlugin.log(e, "Android Framework Parser: failed to collect preference classes"); //$NON-NLS-1$
AdtPlugin.printErrorToConsole("Android Framework Parser",
"failed to collect preference classes");
}
}
/**
* Collects all menu definition information from the attrs.xml and returns it.
*
* @param attrsXmlParser The parser of the attrs.xml file
*/
private Map<String, DeclareStyleableInfo> collectMenuDefinitions(
AttrsXmlParser attrsXmlParser) {
Map<String, DeclareStyleableInfo> map = attrsXmlParser.getDeclareStyleableList();
Map<String, DeclareStyleableInfo> map2 = new HashMap<String, DeclareStyleableInfo>();
for (String key : new String[] { "Menu", //$NON-NLS-1$
"MenuItem", //$NON-NLS-1$
"MenuGroup" }) { //$NON-NLS-1$
if (map.containsKey(key)) {
map2.put(key, map.get(key));
} else {
AdtPlugin.log(IStatus.WARNING,
"Menu declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
key, attrsXmlParser.getOsAttrsXmlPath());
AdtPlugin.printErrorToConsole("Android Framework Parser",
String.format("Menu declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
key, attrsXmlParser.getOsAttrsXmlPath()));
}
}
return Collections.unmodifiableMap(map2);
}
/**
* Collects all searchable definition information from the attrs.xml and returns it.
*
* @param attrsXmlParser The parser of the attrs.xml file
*/
private Map<String, DeclareStyleableInfo> collectSearchableDefinitions(
AttrsXmlParser attrsXmlParser) {
Map<String, DeclareStyleableInfo> map = attrsXmlParser.getDeclareStyleableList();
Map<String, DeclareStyleableInfo> map2 = new HashMap<String, DeclareStyleableInfo>();
for (String key : new String[] { "Searchable", //$NON-NLS-1$
"SearchableActionKey" }) { //$NON-NLS-1$
if (map.containsKey(key)) {
map2.put(key, map.get(key));
} else {
AdtPlugin.log(IStatus.WARNING,
"Searchable declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
key, attrsXmlParser.getOsAttrsXmlPath());
AdtPlugin.printErrorToConsole("Android Framework Parser",
String.format("Searchable declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
key, attrsXmlParser.getOsAttrsXmlPath()));
}
}
return Collections.unmodifiableMap(map2);
}
/**
* Collects all appWidgetProviderInfo definition information from the attrs.xml and returns it.
*
* @param attrsXmlParser The parser of the attrs.xml file
*/
private Map<String, DeclareStyleableInfo> collectAppWidgetDefinitions(
AttrsXmlParser attrsXmlParser) {
Map<String, DeclareStyleableInfo> map = attrsXmlParser.getDeclareStyleableList();
Map<String, DeclareStyleableInfo> map2 = new HashMap<String, DeclareStyleableInfo>();
for (String key : new String[] { "AppWidgetProviderInfo" }) { //$NON-NLS-1$
if (map.containsKey(key)) {
map2.put(key, map.get(key));
} else {
AdtPlugin.log(IStatus.WARNING,
"AppWidget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
key, attrsXmlParser.getOsAttrsXmlPath());
AdtPlugin.printErrorToConsole("Android Framework Parser",
String.format("AppWidget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
key, attrsXmlParser.getOsAttrsXmlPath()));
}
}
return Collections.unmodifiableMap(map2);
}
/**
* Collects all manifest definition information from the attrs_manifest.xml and returns it.
*/
private Map<String, DeclareStyleableInfo> collectManifestDefinitions(
AttrsXmlParser attrsXmlParser) {
return attrsXmlParser.getDeclareStyleableList();
}
}