aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk')
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtConsoleSdkLog.java59
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtManifestMergeCallback.java46
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java458
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java417
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java605
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/IAndroidClassLoader.java81
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParser.java378
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java740
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java1620
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/WidgetClassLoader.java343
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/layout-devices.xsd345
11 files changed, 5092 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtConsoleSdkLog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtConsoleSdkLog.java
new file mode 100755
index 000000000..2396a4c46
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtConsoleSdkLog.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 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.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.utils.ILogger;
+
+/**
+ * An {@link ILogger} logger that outputs to the ADT console.
+ */
+public class AdtConsoleSdkLog implements ILogger {
+
+ private static final String TAG = "SDK Manager"; //$NON-NLS-1$
+
+ @Override
+ public void error(@Nullable Throwable t, @Nullable String errorFormat, Object... args) {
+ if (t != null) {
+ AdtPlugin.logAndPrintError(t, TAG, "Error: " + errorFormat, args);
+ } else {
+ AdtPlugin.printErrorToConsole(TAG, String.format(errorFormat, args));
+ }
+ }
+
+ @Override
+ public void info(@NonNull String msgFormat, Object... args) {
+ String msg = String.format(msgFormat, args);
+ for (String s : msg.split("\n")) {
+ if (s.trim().length() > 0) {
+ AdtPlugin.printToConsole(TAG, s);
+ }
+ }
+ }
+
+ @Override
+ public void verbose(@NonNull String msgFormat, Object... args) {
+ info(msgFormat, args);
+ }
+
+ @Override
+ public void warning(@NonNull String warningFormat, Object... args) {
+ AdtPlugin.printToConsole(TAG, String.format("Warning: " + warningFormat, args));
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtManifestMergeCallback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtManifestMergeCallback.java
new file mode 100755
index 000000000..dc539dcaa
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtManifestMergeCallback.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 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.annotations.NonNull;
+import com.android.manifmerger.ICallback;
+import com.android.manifmerger.ManifestMerger;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+
+/**
+ * A {@link ManifestMerger} {@link ICallback} that returns the
+ * proper API level for known API codenames.
+ */
+public class AdtManifestMergeCallback implements ICallback {
+ @Override
+ public int queryCodenameApiLevel(@NonNull String codename) {
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ try {
+ AndroidVersion version = new AndroidVersion(codename);
+ String hashString = AndroidTargetHash.getPlatformHashString(version);
+ IAndroidTarget t = sdk.getTargetFromHashString(hashString);
+ if (t != null) {
+ return t.getVersion().getApiLevel();
+ }
+ } catch (AndroidVersion.AndroidVersionException ignore) {}
+ }
+ return ICallback.UNKNOWN_CODENAME;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java
new file mode 100644
index 000000000..754cedf79
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java
@@ -0,0 +1,458 @@
+/*
+ * 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 com.android.SdkConstants;
+import com.google.common.io.Closeables;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import javax.management.InvalidAttributeValueException;
+
+/**
+ * Custom class loader able to load a class from the SDK jar file.
+ */
+public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader {
+
+ /**
+ * Wrapper around a {@link Class} to provide the methods of
+ * {@link IAndroidClassLoader.IClassDescriptor}.
+ */
+ public final static class ClassWrapper implements IClassDescriptor {
+ private Class<?> mClass;
+
+ public ClassWrapper(Class<?> clazz) {
+ mClass = clazz;
+ }
+
+ @Override
+ public String getFullClassName() {
+ return mClass.getCanonicalName();
+ }
+
+ @Override
+ public IClassDescriptor[] getDeclaredClasses() {
+ Class<?>[] classes = mClass.getDeclaredClasses();
+ IClassDescriptor[] iclasses = new IClassDescriptor[classes.length];
+ for (int i = 0 ; i < classes.length ; i++) {
+ iclasses[i] = new ClassWrapper(classes[i]);
+ }
+
+ return iclasses;
+ }
+
+ @Override
+ public IClassDescriptor getEnclosingClass() {
+ return new ClassWrapper(mClass.getEnclosingClass());
+ }
+
+ @Override
+ public String getSimpleName() {
+ return mClass.getSimpleName();
+ }
+
+ @Override
+ public IClassDescriptor getSuperclass() {
+ return new ClassWrapper(mClass.getSuperclass());
+ }
+
+ @Override
+ public boolean equals(Object clazz) {
+ if (clazz instanceof ClassWrapper) {
+ return mClass.equals(((ClassWrapper)clazz).mClass);
+ }
+ return super.equals(clazz);
+ }
+
+ @Override
+ public int hashCode() {
+ return mClass.hashCode();
+ }
+
+
+ @Override
+ public boolean isInstantiable() {
+ int modifiers = mClass.getModifiers();
+ return Modifier.isAbstract(modifiers) == false && Modifier.isPublic(modifiers) == true;
+ }
+
+ public Class<?> wrappedClass() {
+ return mClass;
+ }
+
+ }
+
+ private String mOsFrameworkLocation;
+
+ /** A cache for binary data extracted from the zip */
+ private final HashMap<String, byte[]> mEntryCache = new HashMap<String, byte[]>();
+ /** A cache for already defined Classes */
+ private final HashMap<String, Class<?> > mClassCache = new HashMap<String, Class<?> >();
+
+ /**
+ * Creates the class loader by providing the os path to the framework jar archive
+ *
+ * @param osFrameworkLocation OS Path of the framework JAR file
+ */
+ public AndroidJarLoader(String osFrameworkLocation) {
+ super();
+ mOsFrameworkLocation = osFrameworkLocation;
+ }
+
+ @Override
+ public String getSource() {
+ return mOsFrameworkLocation;
+ }
+
+ /**
+ * Pre-loads all class binary data that belong to the given package by reading the archive
+ * once and caching them internally.
+ * <p/>
+ * This does not actually preload "classes", it just reads the unzipped bytes for a given
+ * class. To obtain a class, one must call {@link #findClass(String)} later.
+ * <p/>
+ * All classes which package name starts with "packageFilter" will be included and can be
+ * found later.
+ * <p/>
+ * May throw some exceptions if the framework JAR cannot be read.
+ *
+ * @param packageFilter The package that contains all the class data to preload, using a fully
+ * qualified binary name (.e.g "com.my.package."). The matching algorithm
+ * is simple "startsWith". Use an empty string to include everything.
+ * @param taskLabel An optional task name for the sub monitor. Can be null.
+ * @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
+ * @throws IOException
+ * @throws InvalidAttributeValueException
+ * @throws ClassFormatError
+ */
+ public void preLoadClasses(String packageFilter, String taskLabel, IProgressMonitor monitor)
+ throws IOException, InvalidAttributeValueException, ClassFormatError {
+ // Transform the package name into a zip entry path
+ String pathFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ SubMonitor progress = SubMonitor.convert(monitor, taskLabel == null ? "" : taskLabel, 100);
+
+ // create streams to read the intermediary archive
+ FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
+ ZipInputStream zis = new ZipInputStream(fis);
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ // get the name of the entry.
+ String entryPath = entry.getName();
+
+ if (!entryPath.endsWith(SdkConstants.DOT_CLASS)) {
+ // only accept class files
+ continue;
+ }
+
+ // check if it is part of the package to preload
+ if (pathFilter.length() > 0 && !entryPath.startsWith(pathFilter)) {
+ continue;
+ }
+ String className = entryPathToClassName(entryPath);
+
+ if (!mEntryCache.containsKey(className)) {
+ long entrySize = entry.getSize();
+ if (entrySize > Integer.MAX_VALUE) {
+ throw new InvalidAttributeValueException();
+ }
+ byte[] data = readZipData(zis, (int)entrySize);
+ mEntryCache.put(className, data);
+ }
+
+ // advance 5% of whatever is allocated on the progress bar
+ progress.setWorkRemaining(100);
+ progress.worked(5);
+ progress.subTask(String.format("Preload %1$s", className));
+ }
+ }
+
+ /**
+ * Finds and loads all classes that derive from a given set of super classes.
+ * <p/>
+ * As a side-effect this will load and cache most, if not all, classes in the input JAR file.
+ *
+ * @param packageFilter Base name of package of classes to find.
+ * Use an empty string to find everyting.
+ * @param superClasses The super classes of all the classes to find.
+ * @return An hash map which keys are the super classes looked for and which values are
+ * ArrayList of the classes found. The array lists are always created for all the
+ * valid keys, they are simply empty if no deriving class is found for a given
+ * super class.
+ * @throws IOException
+ * @throws InvalidAttributeValueException
+ * @throws ClassFormatError
+ */
+ @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly
+ @Override
+ public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(
+ String packageFilter,
+ String[] superClasses)
+ throws IOException, InvalidAttributeValueException, ClassFormatError {
+
+ packageFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ HashMap<String, ArrayList<IClassDescriptor>> mClassesFound =
+ new HashMap<String, ArrayList<IClassDescriptor>>();
+
+ for (String className : superClasses) {
+ mClassesFound.put(className, new ArrayList<IClassDescriptor>());
+ }
+
+ // create streams to read the intermediary archive
+ FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
+ ZipInputStream zis = new ZipInputStream(fis);
+ try {
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ // get the name of the entry and convert to a class binary name
+ String entryPath = entry.getName();
+ if (!entryPath.endsWith(SdkConstants.DOT_CLASS)) {
+ // only accept class files
+ continue;
+ }
+ if (packageFilter.length() > 0 && !entryPath.startsWith(packageFilter)) {
+ // only accept stuff from the requested root package.
+ continue;
+ }
+ String className = entryPathToClassName(entryPath);
+
+ Class<?> loaded_class = mClassCache.get(className);
+ if (loaded_class == null) {
+ byte[] data = mEntryCache.get(className);
+ if (data == null) {
+ // Get the class and cache it
+ long entrySize = entry.getSize();
+ if (entrySize > Integer.MAX_VALUE) {
+ throw new InvalidAttributeValueException();
+ }
+ data = readZipData(zis, (int)entrySize);
+ }
+ try {
+ loaded_class = defineAndCacheClass(className, data);
+ } catch (NoClassDefFoundError error) {
+ if (error.getMessage().startsWith("java/")) {
+ // Can't define these; we just need to stop
+ // iteration here
+ continue;
+ }
+ throw error;
+ }
+ }
+
+ for (Class<?> superClass = loaded_class.getSuperclass();
+ superClass != null;
+ superClass = superClass.getSuperclass()) {
+ String superName = superClass.getCanonicalName();
+ if (mClassesFound.containsKey(superName)) {
+ mClassesFound.get(superName).add(new ClassWrapper(loaded_class));
+ break;
+ }
+ }
+ }
+ } finally {
+ Closeables.closeQuietly(zis);
+ }
+
+ return mClassesFound;
+ }
+
+ /** Helper method that converts a Zip entry path into a corresponding
+ * Java full qualified binary class name.
+ * <p/>
+ * F.ex, this converts "com/my/package/Foo.class" into "com.my.package.Foo".
+ */
+ private String entryPathToClassName(String entryPath) {
+ return entryPath.replaceFirst("\\.class$", "").replaceAll("[/\\\\]", "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ /**
+ * Finds the class with the specified binary name.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ try {
+ // try to find the class in the cache
+ Class<?> cached_class = mClassCache.get(name);
+ if (cached_class == ClassNotFoundException.class) {
+ // we already know we can't find this class, don't try again
+ throw new ClassNotFoundException(name);
+ } else if (cached_class != null) {
+ return cached_class;
+ }
+
+ // if not found, look it up and cache it
+ byte[] data = loadClassData(name);
+ if (data != null) {
+ return defineAndCacheClass(name, data);
+ } else {
+ // if the class can't be found, record a CNFE class in the map so
+ // that we don't try to reload it next time
+ mClassCache.put(name, ClassNotFoundException.class);
+ throw new ClassNotFoundException(name);
+ }
+ } catch (ClassNotFoundException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ClassNotFoundException(e.getMessage());
+ }
+ }
+
+ /**
+ * Defines a class based on its binary data and caches the resulting class object.
+ *
+ * @param name The binary name of the class (i.e. package.class1$class2)
+ * @param data The binary data from the loader.
+ * @return The class defined
+ * @throws ClassFormatError if defineClass failed.
+ */
+ private Class<?> defineAndCacheClass(String name, byte[] data) throws ClassFormatError {
+ Class<?> cached_class;
+ cached_class = defineClass(null, data, 0, data.length);
+
+ if (cached_class != null) {
+ // Add new class to the cache class and remove it from the zip entry data cache
+ mClassCache.put(name, cached_class);
+ mEntryCache.remove(name);
+ }
+ return cached_class;
+ }
+
+ /**
+ * Loads a class data from its binary name.
+ * <p/>
+ * This uses the class binary data that has been preloaded earlier by the preLoadClasses()
+ * method if possible.
+ *
+ * @param className the binary name
+ * @return an array of bytes representing the class data or null if not found
+ * @throws InvalidAttributeValueException
+ * @throws IOException
+ */
+ private synchronized byte[] loadClassData(String className)
+ throws InvalidAttributeValueException, IOException {
+
+ byte[] data = mEntryCache.get(className);
+ if (data != null) {
+ return data;
+ }
+
+ // The name is a binary name. Something like "android.R", or "android.R$id".
+ // Make a path out of it.
+ String entryName = className.replaceAll("\\.", "/") + SdkConstants.DOT_CLASS; //$NON-NLS-1$ //$NON-NLS-2$
+
+ // create streams to read the intermediary archive
+ FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
+ ZipInputStream zis = new ZipInputStream(fis);
+ try {
+ // loop on the entries of the intermediary package and put them in the final package.
+ ZipEntry entry;
+
+ while ((entry = zis.getNextEntry()) != null) {
+ // get the name of the entry.
+ String currEntryName = entry.getName();
+
+ if (currEntryName.equals(entryName)) {
+ long entrySize = entry.getSize();
+ if (entrySize > Integer.MAX_VALUE) {
+ throw new InvalidAttributeValueException();
+ }
+
+ data = readZipData(zis, (int)entrySize);
+ return data;
+ }
+ }
+
+ return null;
+ } finally {
+ zis.close();
+ }
+ }
+
+ /**
+ * Reads data for the <em>current</em> entry from the zip input stream.
+ *
+ * @param zis The Zip input stream
+ * @param entrySize The entry size. -1 if unknown.
+ * @return The new data for the <em>current</em> entry.
+ * @throws IOException If ZipInputStream.read() fails.
+ */
+ private byte[] readZipData(ZipInputStream zis, int entrySize) throws IOException {
+ int block_size = 1024;
+ int data_size = entrySize < 1 ? block_size : entrySize;
+ int offset = 0;
+ byte[] data = new byte[data_size];
+
+ while(zis.available() != 0) {
+ int count = zis.read(data, offset, data_size - offset);
+ if (count < 0) { // read data is done
+ break;
+ }
+ offset += count;
+
+ if (entrySize >= 1 && offset >= entrySize) { // we know the size and we're done
+ break;
+ }
+
+ // if we don't know the entry size and we're not done reading,
+ // expand the data buffer some more.
+ if (offset >= data_size) {
+ byte[] temp = new byte[data_size + block_size];
+ System.arraycopy(data, 0, temp, 0, data_size);
+ data_size += block_size;
+ data = temp;
+ block_size *= 2;
+ }
+ }
+
+ if (offset < data_size) {
+ // buffer was allocated too large, trim it
+ byte[] temp = new byte[offset];
+ if (offset > 0) {
+ System.arraycopy(data, 0, temp, 0, offset);
+ }
+ data = temp;
+ }
+
+ return data;
+ }
+
+ /**
+ * Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name.
+ * @param className the fully-qualified name of the class to return.
+ * @throws ClassNotFoundException
+ */
+ @Override
+ public IClassDescriptor getClass(String className) throws ClassNotFoundException {
+ try {
+ return new ClassWrapper(loadClass(className));
+ } catch (ClassNotFoundException e) {
+ throw e; // useful for debugging
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java
new file mode 100644
index 000000000..85ae9fdc0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java
@@ -0,0 +1,417 @@
+/*
+ * 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 com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.LayoutLibrary;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.platform.AttributeInfo;
+import com.android.ide.common.sdk.LoadStatus;
+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.descriptors.IDescriptorProvider;
+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.editors.values.descriptors.ValuesDescriptors;
+import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
+
+import org.eclipse.core.runtime.IStatus;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Map;
+
+/**
+ * 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_OTHER_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;
+ public final static int DESCRIPTOR_DRAWABLE = 9;
+ public final static int DESCRIPTOR_ANIMATOR = 10;
+ public final static int DESCRIPTOR_ANIM = 11;
+ public final static int DESCRIPTOR_COLOR = 12;
+
+ 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 SdkConstants.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 AndroidManifestDescriptors mManifestDescriptors;
+ private DrawableDescriptors mDrawableDescriptors;
+ private AnimatorDescriptors mAnimatorDescriptors;
+ private AnimDescriptors mAnimDescriptors;
+ private ColorDescriptors mColorDescriptors;
+ private LayoutDescriptors mLayoutDescriptors;
+ private MenuDescriptors mMenuDescriptors;
+ private OtherXmlDescriptors mOtherXmlDescriptors;
+
+ private Map<String, Map<String, Integer>> mEnumValueMap;
+
+ private ResourceRepository mFrameworkResources;
+ private LayoutLibrary mLayoutLibrary;
+ private Map<String, AttributeInfo> mAttributeMap;
+
+ private boolean mLayoutBridgeInit = false;
+
+ AndroidTargetData(IAndroidTarget androidTarget) {
+ mTarget = androidTarget;
+ }
+
+ /**
+ * Sets the associated map from string attribute name to
+ * {@link AttributeInfo}
+ *
+ * @param attributeMap the map
+ */
+ public void setAttributeMap(@NonNull Map<String, AttributeInfo> attributeMap) {
+ mAttributeMap = attributeMap;
+ }
+
+ /**
+ * Returns the associated map from string attribute name to
+ * {@link AttributeInfo}
+ *
+ * @return the map
+ */
+ @Nullable
+ public Map<String, AttributeInfo> getAttributeMap() {
+ return mAttributeMap;
+ }
+
+ /**
+ * Creates an AndroidTargetData object.
+ */
+ void setExtraData(
+ AndroidManifestDescriptors manifestDescriptors,
+ LayoutDescriptors layoutDescriptors,
+ MenuDescriptors menuDescriptors,
+ OtherXmlDescriptors otherXmlDescriptors,
+ DrawableDescriptors drawableDescriptors,
+ AnimatorDescriptors animatorDescriptors,
+ AnimDescriptors animDescriptors,
+ ColorDescriptors colorDescriptors,
+ Map<String, Map<String, Integer>> enumValueMap,
+ String[] permissionValues,
+ String[] activityIntentActionValues,
+ String[] broadcastIntentActionValues,
+ String[] serviceIntentActionValues,
+ String[] intentCategoryValues,
+ String[] platformLibraries,
+ IOptionalLibrary[] optionalLibraries,
+ ResourceRepository frameworkResources,
+ LayoutLibrary layoutLibrary) {
+
+ mManifestDescriptors = manifestDescriptors;
+ mDrawableDescriptors = drawableDescriptors;
+ mAnimatorDescriptors = animatorDescriptors;
+ mAnimDescriptors = animDescriptors;
+ mColorDescriptors = colorDescriptors;
+ mLayoutDescriptors = layoutDescriptors;
+ mMenuDescriptors = menuDescriptors;
+ mOtherXmlDescriptors = otherXmlDescriptors;
+ mEnumValueMap = enumValueMap;
+ mFrameworkResources = frameworkResources;
+ mLayoutLibrary = layoutLibrary;
+
+ setPermissions(permissionValues);
+ setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues,
+ serviceIntentActionValues, intentCategoryValues);
+ setOptionalLibraries(platformLibraries, optionalLibraries);
+ }
+
+ /**
+ * 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_OTHER_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_OTHER_XML:
+ return mOtherXmlDescriptors;
+ case DESCRIPTOR_RESOURCES:
+ // FIXME: since it's hard-coded the Resources Descriptors are not platform dependent.
+ return ValuesDescriptors.getInstance();
+ case DESCRIPTOR_PREFERENCES:
+ return mOtherXmlDescriptors.getPreferencesProvider();
+ case DESCRIPTOR_APPWIDGET_PROVIDER:
+ return mOtherXmlDescriptors.getAppWidgetProvider();
+ case DESCRIPTOR_SEARCHABLE:
+ return mOtherXmlDescriptors.getSearchableProvider();
+ case DESCRIPTOR_DRAWABLE:
+ return mDrawableDescriptors;
+ case DESCRIPTOR_ANIMATOR:
+ return mAnimatorDescriptors;
+ case DESCRIPTOR_ANIM:
+ return mAnimDescriptors;
+ case DESCRIPTOR_COLOR:
+ return mColorDescriptors;
+ default :
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Returns the manifest descriptors.
+ */
+ public AndroidManifestDescriptors getManifestDescriptors() {
+ return mManifestDescriptors;
+ }
+
+ /**
+ * Returns the drawable descriptors
+ */
+ public DrawableDescriptors getDrawableDescriptors() {
+ return mDrawableDescriptors;
+ }
+
+ /**
+ * Returns the animation descriptors
+ */
+ public AnimDescriptors getAnimDescriptors() {
+ return mAnimDescriptors;
+ }
+
+ /**
+ * Returns the color descriptors
+ */
+ public ColorDescriptors getColorDescriptors() {
+ return mColorDescriptors;
+ }
+
+ /**
+ * Returns the animator descriptors
+ */
+ public AnimatorDescriptors getAnimatorDescriptors() {
+ return mAnimatorDescriptors;
+ }
+
+ /**
+ * Returns the layout Descriptors.
+ */
+ public LayoutDescriptors getLayoutDescriptors() {
+ return mLayoutDescriptors;
+ }
+
+ /**
+ * Returns the menu descriptors.
+ */
+ public MenuDescriptors getMenuDescriptors() {
+ return mMenuDescriptors;
+ }
+
+ /**
+ * Returns the XML descriptors
+ */
+ public OtherXmlDescriptors getXmlDescriptors() {
+ return mOtherXmlDescriptors;
+ }
+
+ /**
+ * 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 ResourceRepository 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);
+ }
+
+ public void dispose() {
+ if (mLayoutLibrary != null) {
+ mLayoutLibrary.dispose();
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java
new file mode 100644
index 000000000..9a1fd3dc9
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java
@@ -0,0 +1,605 @@
+/*
+ * 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();
+ }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/IAndroidClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/IAndroidClassLoader.java
new file mode 100644
index 000000000..ab78d2a9b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/IAndroidClassLoader.java
@@ -0,0 +1,81 @@
+/*
+ * 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 java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import javax.management.InvalidAttributeValueException;
+
+/**
+ * Classes which implements this interface provide methods to access framework resource
+ * data loaded from the SDK.
+ */
+interface IAndroidClassLoader {
+
+ /**
+ * Classes which implement this interface provide methods to describe a class.
+ */
+ public interface IClassDescriptor {
+
+ String getFullClassName();
+
+ IClassDescriptor getSuperclass();
+
+ String getSimpleName();
+
+ IClassDescriptor getEnclosingClass();
+
+ IClassDescriptor[] getDeclaredClasses();
+
+ boolean isInstantiable();
+ }
+
+ /**
+ * Finds and loads all classes that derive from a given set of super classes.
+ *
+ * @param rootPackage Root package of classes to find. Use an empty string to find everyting.
+ * @param superClasses The super classes of all the classes to find.
+ * @return An hash map which keys are the super classes looked for and which values are
+ * ArrayList of the classes found. The array lists are always created for all the
+ * valid keys, they are simply empty if no deriving class is found for a given
+ * super class.
+ * @throws IOException
+ * @throws InvalidAttributeValueException
+ * @throws ClassFormatError
+ */
+ public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(
+ String rootPackage, String[] superClasses)
+ throws IOException, InvalidAttributeValueException, ClassFormatError;
+
+ /**
+ * Returns a {@link IClassDescriptor} by its fully-qualified name.
+ * @param className the fully-qualified name of the class to return.
+ * @throws ClassNotFoundException
+ */
+ public IClassDescriptor getClass(String className) throws ClassNotFoundException;
+
+ /**
+ * Returns a string indicating the source of the classes, typically for debugging
+ * or in error messages. This would typically be a JAR file name or some kind of
+ * identifier that would mean something to the user when looking at error messages.
+ *
+ * @return An informal string representing the source of the classes.
+ */
+ public String getSource();
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParser.java
new file mode 100644
index 000000000..d05c12a9e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParser.java
@@ -0,0 +1,378 @@
+/*
+ * 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.resources.platform.AttrsXmlParser;
+import com.android.ide.common.resources.platform.ViewClassInfo;
+import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.sdk.IAndroidClassLoader.IClassDescriptor;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.management.InvalidAttributeValueException;
+
+/*
+ * TODO: refactor this. Could use some cleanup.
+ */
+
+/**
+ * Parser for the framework library.
+ * <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 class LayoutParamsParser {
+
+ /**
+ * Class extending {@link ViewClassInfo} by adding the notion of instantiability.
+ * {@link LayoutParamsParser#getViews()} and {@link LayoutParamsParser#getGroups()} should
+ * only return classes that can be instantiated.
+ */
+ final static class ExtViewClassInfo extends ViewClassInfo {
+
+ private boolean mIsInstantiable;
+
+ ExtViewClassInfo(boolean instantiable, boolean isLayout, String canonicalClassName,
+ String shortClassName) {
+ super(isLayout, canonicalClassName, shortClassName);
+ mIsInstantiable = instantiable;
+ }
+
+ boolean isInstantiable() {
+ return mIsInstantiable;
+ }
+ }
+
+ /* Note: protected members/methods are overridden in unit tests */
+
+ /** Reference to android.view.View */
+ protected IClassDescriptor mTopViewClass;
+ /** Reference to android.view.ViewGroup */
+ protected IClassDescriptor mTopGroupClass;
+ /** Reference to android.view.ViewGroup$LayoutParams */
+ protected IClassDescriptor mTopLayoutParamsClass;
+
+ /** Input list of all classes deriving from android.view.View */
+ protected ArrayList<IClassDescriptor> mViewList;
+ /** Input list of all classes deriving from android.view.ViewGroup */
+ protected ArrayList<IClassDescriptor> mGroupList;
+
+ /** Output map of FQCN => info on View classes */
+ protected TreeMap<String, ExtViewClassInfo> mViewMap;
+ /** Output map of FQCN => info on ViewGroup classes */
+ protected TreeMap<String, ExtViewClassInfo> mGroupMap;
+ /** Output map of FQCN => info on LayoutParams classes */
+ protected HashMap<String, LayoutParamsInfo> mLayoutParamsMap;
+
+ /** The attrs.xml parser */
+ protected AttrsXmlParser mAttrsXmlParser;
+
+ /** The android.jar class loader */
+ protected IAndroidClassLoader mClassLoader;
+
+ /**
+ * Instantiate a new LayoutParamsParser.
+ * @param classLoader The android.jar class loader
+ * @param attrsXmlParser The parser of the attrs.xml file
+ */
+ public LayoutParamsParser(IAndroidClassLoader classLoader,
+ AttrsXmlParser attrsXmlParser) {
+ mClassLoader = classLoader;
+ mAttrsXmlParser = attrsXmlParser;
+ }
+
+ /** Returns the map of FQCN => info on View classes */
+ public List<ViewClassInfo> getViews() {
+ return getInstantiables(mViewMap);
+ }
+
+ /** Returns the map of FQCN => info on ViewGroup classes */
+ public List<ViewClassInfo> getGroups() {
+ return getInstantiables(mGroupMap);
+ }
+
+ /**
+ * TODO: doc here.
+ * <p/>
+ * Note: on output we should have NO dependency on {@link IClassDescriptor},
+ * otherwise we wouldn't be able to unload the class loader later.
+ * <p/>
+ * Note on Vocabulary: FQCN=Fully Qualified Class Name (e.g. "my.package.class$innerClass")
+ * @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
+ */
+ public void parseLayoutClasses(IProgressMonitor monitor) {
+ parseClasses(monitor,
+ SdkConstants.CLASS_VIEW,
+ SdkConstants.CLASS_VIEWGROUP,
+ SdkConstants.CLASS_VIEWGROUP_LAYOUTPARAMS);
+ }
+
+ public void parsePreferencesClasses(IProgressMonitor monitor) {
+ parseClasses(monitor,
+ SdkConstants.CLASS_PREFERENCE,
+ SdkConstants.CLASS_PREFERENCEGROUP,
+ null /* paramsClassName */ );
+ }
+
+ private void parseClasses(IProgressMonitor monitor,
+ String rootClassName,
+ String groupClassName,
+ String paramsClassName) {
+ try {
+ SubMonitor progress = SubMonitor.convert(monitor, 100);
+
+ String[] superClasses = new String[2 + (paramsClassName == null ? 0 : 1)];
+ superClasses[0] = groupClassName;
+ superClasses[1] = rootClassName;
+ if (paramsClassName != null) {
+ superClasses[2] = paramsClassName;
+ }
+ HashMap<String, ArrayList<IClassDescriptor>> found =
+ mClassLoader.findClassesDerivingFrom("android.", superClasses); //$NON-NLS-1$
+ mTopViewClass = mClassLoader.getClass(rootClassName);
+ mTopGroupClass = mClassLoader.getClass(groupClassName);
+ if (paramsClassName != null) {
+ mTopLayoutParamsClass = mClassLoader.getClass(paramsClassName);
+ }
+
+ mViewList = found.get(rootClassName);
+ mGroupList = found.get(groupClassName);
+
+ mViewMap = new TreeMap<String, ExtViewClassInfo>();
+ mGroupMap = new TreeMap<String, ExtViewClassInfo>();
+ if (mTopLayoutParamsClass != null) {
+ mLayoutParamsMap = new HashMap<String, LayoutParamsInfo>();
+ }
+
+ // Add top classes to the maps since by design they are not listed in classes deriving
+ // from themselves.
+ if (mTopGroupClass != null) {
+ addGroup(mTopGroupClass);
+ }
+ if (mTopViewClass != null) {
+ addView(mTopViewClass);
+ }
+
+ // ViewGroup derives from View
+ ExtViewClassInfo vg = mGroupMap.get(groupClassName);
+ if (vg != null) {
+ vg.setSuperClass(mViewMap.get(rootClassName));
+ }
+
+ progress.setWorkRemaining(mGroupList.size() + mViewList.size());
+
+ for (IClassDescriptor groupChild : mGroupList) {
+ addGroup(groupChild);
+ progress.worked(1);
+ }
+
+ for (IClassDescriptor viewChild : mViewList) {
+ if (viewChild != mTopGroupClass) {
+ addView(viewChild);
+ }
+ progress.worked(1);
+ }
+ } catch (ClassNotFoundException e) {
+ AdtPlugin.log(e, "Problem loading class %1$s or %2$s", //$NON-NLS-1$
+ rootClassName, groupClassName);
+ } catch (InvalidAttributeValueException e) {
+ AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
+ } catch (ClassFormatError e) {
+ AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
+ } catch (IOException e) {
+ AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Parses a View class and adds a ExtViewClassInfo for it in mViewMap.
+ * It calls itself recursively to handle super classes which are also Views.
+ */
+ private ExtViewClassInfo addView(IClassDescriptor viewClass) {
+ String fqcn = viewClass.getFullClassName();
+ if (mViewMap.containsKey(fqcn)) {
+ return mViewMap.get(fqcn);
+ } else if (mGroupMap.containsKey(fqcn)) {
+ return mGroupMap.get(fqcn);
+ }
+
+ ExtViewClassInfo info = new ExtViewClassInfo(viewClass.isInstantiable(),
+ false /* layout */, fqcn, viewClass.getSimpleName());
+ mViewMap.put(fqcn, info);
+
+ // All view classes derive from mTopViewClass by design.
+ // Do not lookup the super class for mTopViewClass itself.
+ if (viewClass.equals(mTopViewClass) == false) {
+ IClassDescriptor superClass = viewClass.getSuperclass();
+ ExtViewClassInfo superClassInfo = addView(superClass);
+ info.setSuperClass(superClassInfo);
+ }
+
+ mAttrsXmlParser.loadViewAttributes(info);
+ return info;
+ }
+
+ /**
+ * Parses a ViewGroup class and adds a ExtViewClassInfo for it in mGroupMap.
+ * It calls itself recursively to handle super classes which are also ViewGroups.
+ */
+ private ExtViewClassInfo addGroup(IClassDescriptor groupClass) {
+ String fqcn = groupClass.getFullClassName();
+ if (mGroupMap.containsKey(fqcn)) {
+ return mGroupMap.get(fqcn);
+ }
+
+ ExtViewClassInfo info = new ExtViewClassInfo(groupClass.isInstantiable(),
+ true /* layout */, fqcn, groupClass.getSimpleName());
+ mGroupMap.put(fqcn, info);
+
+ // All groups derive from android.view.ViewGroup, which in turns derives from
+ // android.view.View (i.e. mTopViewClass here). So the only group that can have View as
+ // its super class is the ViewGroup base class and we don't try to resolve it since groups
+ // are loaded before views.
+ IClassDescriptor superClass = groupClass.getSuperclass();
+
+ // Assertion: at this point, we should have
+ // superClass != mTopViewClass || fqcn.equals(SdkConstants.CLASS_VIEWGROUP);
+
+ if (superClass != null && superClass.equals(mTopViewClass) == false) {
+ ExtViewClassInfo superClassInfo = addGroup(superClass);
+
+ // Assertion: we should have superClassInfo != null && superClassInfo != info;
+ if (superClassInfo != null && superClassInfo != info) {
+ info.setSuperClass(superClassInfo);
+ }
+ }
+
+ mAttrsXmlParser.loadViewAttributes(info);
+ if (mTopLayoutParamsClass != null) {
+ info.setLayoutParams(addLayoutParams(groupClass));
+ }
+ return info;
+ }
+
+ /**
+ * Parses a ViewGroup class and returns an info object on its inner LayoutParams.
+ *
+ * @return The {@link LayoutParamsInfo} for the ViewGroup class or null.
+ */
+ private LayoutParamsInfo addLayoutParams(IClassDescriptor groupClass) {
+
+ // Is there a LayoutParams in this group class?
+ IClassDescriptor layoutParamsClass = findLayoutParams(groupClass);
+
+ // if there's no layout data in the group class, link to the one from the
+ // super class.
+ if (layoutParamsClass == null) {
+ for (IClassDescriptor superClass = groupClass.getSuperclass();
+ layoutParamsClass == null &&
+ superClass != null &&
+ superClass.equals(mTopViewClass) == false;
+ superClass = superClass.getSuperclass()) {
+ layoutParamsClass = findLayoutParams(superClass);
+ }
+ }
+
+ if (layoutParamsClass != null) {
+ return getLayoutParamsInfo(layoutParamsClass);
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses a LayoutParams class and returns a LayoutParamsInfo object for it.
+ * It calls itself recursively to handle the super class of the LayoutParams.
+ */
+ private LayoutParamsInfo getLayoutParamsInfo(IClassDescriptor layoutParamsClass) {
+ String fqcn = layoutParamsClass.getFullClassName();
+ LayoutParamsInfo layoutParamsInfo = mLayoutParamsMap.get(fqcn);
+
+ if (layoutParamsInfo != null) {
+ return layoutParamsInfo;
+ }
+
+ // Find the link on the LayoutParams super class
+ LayoutParamsInfo superClassInfo = null;
+ if (layoutParamsClass.equals(mTopLayoutParamsClass) == false) {
+ IClassDescriptor superClass = layoutParamsClass.getSuperclass();
+ superClassInfo = getLayoutParamsInfo(superClass);
+ }
+
+ // Find the link on the enclosing ViewGroup
+ ExtViewClassInfo enclosingGroupInfo = addGroup(layoutParamsClass.getEnclosingClass());
+
+ layoutParamsInfo = new ExtViewClassInfo.LayoutParamsInfo(
+ enclosingGroupInfo, layoutParamsClass.getSimpleName(), superClassInfo);
+ mLayoutParamsMap.put(fqcn, layoutParamsInfo);
+
+ mAttrsXmlParser.loadLayoutParamsAttributes(layoutParamsInfo);
+
+ return layoutParamsInfo;
+ }
+
+ /**
+ * Given a ViewGroup-derived class, looks for an inner class named LayoutParams
+ * and if found returns its class definition.
+ * <p/>
+ * This uses the actual defined inner classes and does not look at inherited classes.
+ *
+ * @param groupClass The ViewGroup derived class
+ * @return The Class of the inner LayoutParams or null if none is declared.
+ */
+ private IClassDescriptor findLayoutParams(IClassDescriptor groupClass) {
+ IClassDescriptor[] innerClasses = groupClass.getDeclaredClasses();
+ for (IClassDescriptor innerClass : innerClasses) {
+ if (innerClass.getSimpleName().equals(SdkConstants.CLASS_NAME_LAYOUTPARAMS)) {
+ return innerClass;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Computes and return a list of ViewClassInfo from a map by filtering out the class that
+ * cannot be instantiated.
+ */
+ private List<ViewClassInfo> getInstantiables(SortedMap<String, ExtViewClassInfo> map) {
+ Collection<ExtViewClassInfo> values = map.values();
+ ArrayList<ViewClassInfo> list = new ArrayList<ViewClassInfo>();
+
+ for (ExtViewClassInfo info : values) {
+ if (info.isInstantiable()) {
+ list.add(info);
+ }
+ }
+
+ return list;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java
new file mode 100644
index 000000000..74c985784
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java
@@ -0,0 +1,740 @@
+/*
+ * Copyright (C) 2010 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.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+
+/**
+ * Centralized state for Android Eclipse project.
+ * <p>This gives raw access to the properties (from <code>project.properties</code>), as well
+ * as direct access to target and library information.
+ *
+ * This also gives access to library information.
+ *
+ * {@link #isLibrary()} indicates if the project is a library.
+ * {@link #hasLibraries()} and {@link #getLibraries()} give access to the libraries through
+ * instances of {@link LibraryState}. A {@link LibraryState} instance is a link between a main
+ * project and its library. Theses instances are owned by the {@link ProjectState}.
+ *
+ * {@link #isMissingLibraries()} will indicate if the project has libraries that are not resolved.
+ * Unresolved libraries are libraries that do not have any matching opened Eclipse project.
+ * When there are missing libraries, the {@link LibraryState} instance for them will return null
+ * for {@link LibraryState#getProjectState()}.
+ *
+ */
+public final class ProjectState {
+
+ /**
+ * A class that represents a library linked to a project.
+ * <p/>It does not represent the library uniquely. Instead the {@link LibraryState} is linked
+ * to the main project which is accessible through {@link #getMainProjectState()}.
+ * <p/>If a library is used by two different projects, then there will be two different
+ * instances of {@link LibraryState} for the library.
+ *
+ * @see ProjectState#getLibrary(IProject)
+ */
+ public final class LibraryState {
+ private String mRelativePath;
+ private ProjectState mProjectState;
+ private String mPath;
+
+ private LibraryState(String relativePath) {
+ mRelativePath = relativePath;
+ }
+
+ /**
+ * Returns the {@link ProjectState} of the main project using this library.
+ */
+ public ProjectState getMainProjectState() {
+ return ProjectState.this;
+ }
+
+ /**
+ * Closes the library. This resets the IProject from this object ({@link #getProjectState()} will
+ * return <code>null</code>), and updates the main project data so that the library
+ * {@link IProject} object does not show up in the return value of
+ * {@link ProjectState#getFullLibraryProjects()}.
+ */
+ public void close() {
+ mProjectState.removeParentProject(getMainProjectState());
+ mProjectState = null;
+ mPath = null;
+
+ getMainProjectState().updateFullLibraryList();
+ }
+
+ private void setRelativePath(String relativePath) {
+ mRelativePath = relativePath;
+ }
+
+ private void setProject(ProjectState project) {
+ mProjectState = project;
+ mPath = project.getProject().getLocation().toOSString();
+ mProjectState.addParentProject(getMainProjectState());
+
+ getMainProjectState().updateFullLibraryList();
+ }
+
+ /**
+ * Returns the relative path of the library from the main project.
+ * <p/>This is identical to the value defined in the main project's project.properties.
+ */
+ public String getRelativePath() {
+ return mRelativePath;
+ }
+
+ /**
+ * Returns the {@link ProjectState} item for the library. This can be null if the project
+ * is not actually opened in Eclipse.
+ */
+ public ProjectState getProjectState() {
+ return mProjectState;
+ }
+
+ /**
+ * Returns the OS-String location of the library project.
+ * <p/>This is based on location of the Eclipse project that matched
+ * {@link #getRelativePath()}.
+ *
+ * @return The project location, or null if the project is not opened in Eclipse.
+ */
+ public String getProjectLocation() {
+ return mPath;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof LibraryState) {
+ // the only thing that's always non-null is the relative path.
+ LibraryState objState = (LibraryState)obj;
+ return mRelativePath.equals(objState.mRelativePath) &&
+ getMainProjectState().equals(objState.getMainProjectState());
+ } else if (obj instanceof ProjectState || obj instanceof IProject) {
+ return mProjectState != null && mProjectState.equals(obj);
+ } else if (obj instanceof String) {
+ return normalizePath(mRelativePath).equals(normalizePath((String) obj));
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return normalizePath(mRelativePath).hashCode();
+ }
+ }
+
+ private final IProject mProject;
+ private final ProjectProperties mProperties;
+ private IAndroidTarget mTarget;
+ private BuildToolInfo mBuildToolInfo;
+
+ /**
+ * list of libraries. Access to this list must be protected by
+ * <code>synchronized(mLibraries)</code>, but it is important that such code do not call
+ * out to other classes (especially those protected by {@link Sdk#getLock()}.)
+ */
+ private final ArrayList<LibraryState> mLibraries = new ArrayList<LibraryState>();
+ /** Cached list of all IProject instances representing the resolved libraries, including
+ * indirect dependencies. This must never be null. */
+ private List<IProject> mLibraryProjects = Collections.emptyList();
+ /**
+ * List of parent projects. When this instance is a library ({@link #isLibrary()} returns
+ * <code>true</code>) then this is filled with projects that depends on this project.
+ */
+ private final ArrayList<ProjectState> mParentProjects = new ArrayList<ProjectState>();
+
+ ProjectState(IProject project, ProjectProperties properties) {
+ if (project == null || properties == null) {
+ throw new NullPointerException();
+ }
+
+ mProject = project;
+ mProperties = properties;
+
+ // load the libraries
+ synchronized (mLibraries) {
+ int index = 1;
+ while (true) {
+ String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++);
+ String rootPath = mProperties.getProperty(propName);
+
+ if (rootPath == null) {
+ break;
+ }
+
+ mLibraries.add(new LibraryState(convertPath(rootPath)));
+ }
+ }
+ }
+
+ public IProject getProject() {
+ return mProject;
+ }
+
+ public ProjectProperties getProperties() {
+ return mProperties;
+ }
+
+ public @Nullable String getProperty(@NonNull String name) {
+ if (mProperties != null) {
+ return mProperties.getProperty(name);
+ }
+
+ return null;
+ }
+
+ public void setTarget(IAndroidTarget target) {
+ mTarget = target;
+ }
+
+ /**
+ * Returns the project's target's hash string.
+ * <p/>If {@link #getTarget()} returns a valid object, then this returns the value of
+ * {@link IAndroidTarget#hashString()}.
+ * <p/>Otherwise this will return the value of the property
+ * {@link ProjectProperties#PROPERTY_TARGET} from {@link #getProperties()} (if valid).
+ * @return the target hash string or null if not found.
+ */
+ public String getTargetHashString() {
+ if (mTarget != null) {
+ return mTarget.hashString();
+ }
+
+ return mProperties.getProperty(ProjectProperties.PROPERTY_TARGET);
+ }
+
+ public IAndroidTarget getTarget() {
+ return mTarget;
+ }
+
+ public void setBuildToolInfo(BuildToolInfo buildToolInfo) {
+ mBuildToolInfo = buildToolInfo;
+ }
+
+ public BuildToolInfo getBuildToolInfo() {
+ return mBuildToolInfo;
+ }
+
+ /**
+ * Returns the build tools version from the project's properties.
+ * @return the value or null
+ */
+ @Nullable
+ public String getBuildToolInfoVersion() {
+ return mProperties.getProperty(ProjectProperties.PROPERTY_BUILD_TOOLS);
+ }
+
+ public boolean getRenderScriptSupportMode() {
+ String supportModeValue = mProperties.getProperty(ProjectProperties.PROPERTY_RS_SUPPORT);
+ if (supportModeValue != null) {
+ return Boolean.parseBoolean(supportModeValue);
+ }
+
+ return false;
+ }
+
+ public static class LibraryDifference {
+ public boolean removed = false;
+ public boolean added = false;
+
+ public boolean hasDiff() {
+ return removed || added;
+ }
+ }
+
+ /**
+ * Reloads the content of the properties.
+ * <p/>This also reset the reference to the target as it may have changed, therefore this
+ * should be followed by a call to {@link Sdk#loadTarget(ProjectState)}.
+ *
+ * <p/>If the project libraries changes, they are updated to a certain extent.<br>
+ * Removed libraries are removed from the state list, and added to the {@link LibraryDifference}
+ * object that is returned so that they can be processed.<br>
+ * Added libraries are added to the state (as new {@link LibraryState} objects), but their
+ * IProject is not resolved. {@link ProjectState#needs(ProjectState)} should be called
+ * afterwards to properly initialize the libraries.
+ *
+ * @return an instance of {@link LibraryDifference} describing the change in libraries.
+ */
+ public LibraryDifference reloadProperties() {
+ mTarget = null;
+ mProperties.reload();
+
+ // compare/reload the libraries.
+
+ // if the order change it won't impact the java part, so instead try to detect removed/added
+ // libraries.
+
+ LibraryDifference diff = new LibraryDifference();
+
+ synchronized (mLibraries) {
+ List<LibraryState> oldLibraries = new ArrayList<LibraryState>(mLibraries);
+ mLibraries.clear();
+
+ // load the libraries
+ int index = 1;
+ while (true) {
+ String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++);
+ String rootPath = mProperties.getProperty(propName);
+
+ if (rootPath == null) {
+ break;
+ }
+
+ // search for a library with the same path (not exact same string, but going
+ // to the same folder).
+ String convertedPath = convertPath(rootPath);
+ boolean found = false;
+ for (int i = 0 ; i < oldLibraries.size(); i++) {
+ LibraryState libState = oldLibraries.get(i);
+ if (libState.equals(convertedPath)) {
+ // it's a match. move it back to mLibraries and remove it from the
+ // old library list.
+ found = true;
+ mLibraries.add(libState);
+ oldLibraries.remove(i);
+ break;
+ }
+ }
+
+ if (found == false) {
+ diff.added = true;
+ mLibraries.add(new LibraryState(convertedPath));
+ }
+ }
+
+ // whatever's left in oldLibraries is removed.
+ diff.removed = oldLibraries.size() > 0;
+
+ // update the library with what IProjet are known at the time.
+ updateFullLibraryList();
+ }
+
+ return diff;
+ }
+
+ /**
+ * Returns the list of {@link LibraryState}.
+ */
+ public List<LibraryState> getLibraries() {
+ synchronized (mLibraries) {
+ return Collections.unmodifiableList(mLibraries);
+ }
+ }
+
+ /**
+ * Returns all the <strong>resolved</strong> library projects, including indirect dependencies.
+ * The list is ordered to match the library priority order for resource processing with
+ * <code>aapt</code>.
+ * <p/>If some dependencies are not resolved (or their projects is not opened in Eclipse),
+ * they will not show up in this list.
+ * @return the resolved projects as an unmodifiable list. May be an empty.
+ */
+ public List<IProject> getFullLibraryProjects() {
+ return mLibraryProjects;
+ }
+
+ /**
+ * Returns whether this is a library project.
+ */
+ public boolean isLibrary() {
+ String value = mProperties.getProperty(ProjectProperties.PROPERTY_LIBRARY);
+ return value != null && Boolean.valueOf(value);
+ }
+
+ /**
+ * Returns whether the project depends on one or more libraries.
+ */
+ public boolean hasLibraries() {
+ synchronized (mLibraries) {
+ return mLibraries.size() > 0;
+ }
+ }
+
+ /**
+ * Returns whether the project is missing some required libraries.
+ */
+ public boolean isMissingLibraries() {
+ synchronized (mLibraries) {
+ for (LibraryState state : mLibraries) {
+ if (state.getProjectState() == null) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the {@link LibraryState} object for a given {@link IProject}.
+ * </p>This can only return a non-null object if the link between the main project's
+ * {@link IProject} and the library's {@link IProject} was done.
+ *
+ * @return the matching LibraryState or <code>null</code>
+ *
+ * @see #needs(ProjectState)
+ */
+ public LibraryState getLibrary(IProject library) {
+ synchronized (mLibraries) {
+ for (LibraryState state : mLibraries) {
+ ProjectState ps = state.getProjectState();
+ if (ps != null && ps.getProject().equals(library)) {
+ return state;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the {@link LibraryState} object for a given <var>name</var>.
+ * </p>This can only return a non-null object if the link between the main project's
+ * {@link IProject} and the library's {@link IProject} was done.
+ *
+ * @return the matching LibraryState or <code>null</code>
+ *
+ * @see #needs(IProject)
+ */
+ public LibraryState getLibrary(String name) {
+ synchronized (mLibraries) {
+ for (LibraryState state : mLibraries) {
+ ProjectState ps = state.getProjectState();
+ if (ps != null && ps.getProject().getName().equals(name)) {
+ return state;
+ }
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Returns whether a given library project is needed by the receiver.
+ * <p/>If the library is needed, this finds the matching {@link LibraryState}, initializes it
+ * so that it contains the library's {@link IProject} object (so that
+ * {@link LibraryState#getProjectState()} does not return null) and then returns it.
+ *
+ * @param libraryProject the library project to check.
+ * @return a non null object if the project is a library dependency,
+ * <code>null</code> otherwise.
+ *
+ * @see LibraryState#getProjectState()
+ */
+ public LibraryState needs(ProjectState libraryProject) {
+ // compute current location
+ File projectFile = mProject.getLocation().toFile();
+
+ // get the location of the library.
+ File libraryFile = libraryProject.getProject().getLocation().toFile();
+
+ // loop on all libraries and check if the path match
+ synchronized (mLibraries) {
+ for (LibraryState state : mLibraries) {
+ if (state.getProjectState() == null) {
+ File library = new File(projectFile, state.getRelativePath());
+ try {
+ File absPath = library.getCanonicalFile();
+ if (absPath.equals(libraryFile)) {
+ state.setProject(libraryProject);
+ return state;
+ }
+ } catch (IOException e) {
+ // ignore this library
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns whether the project depends on a given <var>library</var>
+ * @param library the library to check.
+ * @return true if the project depends on the library. This is not affected by whether the link
+ * was done through {@link #needs(ProjectState)}.
+ */
+ public boolean dependsOn(ProjectState library) {
+ synchronized (mLibraries) {
+ for (LibraryState state : mLibraries) {
+ if (state != null && state.getProjectState() != null &&
+ library.getProject().equals(state.getProjectState().getProject())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Updates a library with a new path.
+ * <p/>This method acts both as a check and an action. If the project does not depend on the
+ * given <var>oldRelativePath</var> then no action is done and <code>null</code> is returned.
+ * <p/>If the project depends on the library, then the project is updated with the new path,
+ * and the {@link LibraryState} for the library is returned.
+ * <p/>Updating the project does two things:<ul>
+ * <li>Update LibraryState with new relative path and new {@link IProject} object.</li>
+ * <li>Update the main project's <code>project.properties</code> with the new relative path
+ * for the changed library.</li>
+ * </ul>
+ *
+ * @param oldRelativePath the old library path relative to this project
+ * @param newRelativePath the new library path relative to this project
+ * @param newLibraryState the new {@link ProjectState} object.
+ * @return a non null object if the project depends on the library.
+ *
+ * @see LibraryState#getProjectState()
+ */
+ public LibraryState updateLibrary(String oldRelativePath, String newRelativePath,
+ ProjectState newLibraryState) {
+ // compute current location
+ File projectFile = mProject.getLocation().toFile();
+
+ // loop on all libraries and check if the path matches
+ synchronized (mLibraries) {
+ for (LibraryState state : mLibraries) {
+ if (state.getProjectState() == null) {
+ try {
+ // oldRelativePath may not be the same exact string as the
+ // one in the project properties (trailing separator could be different
+ // for instance).
+ // Use java.io.File to deal with this and also do a platform-dependent
+ // path comparison
+ File library1 = new File(projectFile, oldRelativePath);
+ File library2 = new File(projectFile, state.getRelativePath());
+ if (library1.getCanonicalPath().equals(library2.getCanonicalPath())) {
+ // save the exact property string to replace.
+ String oldProperty = state.getRelativePath();
+
+ // then update the LibraryPath.
+ state.setRelativePath(newRelativePath);
+ state.setProject(newLibraryState);
+
+ // update the project.properties file
+ IStatus status = replaceLibraryProperty(oldProperty, newRelativePath);
+ if (status != null) {
+ if (status.getSeverity() != IStatus.OK) {
+ // log the error somehow.
+ }
+ } else {
+ // This should not happen since the library wouldn't be here in the
+ // first place
+ }
+
+ // return the LibraryState object.
+ return state;
+ }
+ } catch (IOException e) {
+ // ignore this library
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+
+ private void addParentProject(ProjectState parentState) {
+ mParentProjects.add(parentState);
+ }
+
+ private void removeParentProject(ProjectState parentState) {
+ mParentProjects.remove(parentState);
+ }
+
+ public List<ProjectState> getParentProjects() {
+ return Collections.unmodifiableList(mParentProjects);
+ }
+
+ /**
+ * Computes the transitive closure of projects referencing this project as a
+ * library project
+ *
+ * @return a collection (in any order) of project states for projects that
+ * directly or indirectly include this project state's project as a
+ * library project
+ */
+ public Collection<ProjectState> getFullParentProjects() {
+ Set<ProjectState> result = new HashSet<ProjectState>();
+ addParentProjects(result, this);
+ return result;
+ }
+
+ /** Adds all parent projects of the given project, transitively, into the given parent set */
+ private static void addParentProjects(Set<ProjectState> parents, ProjectState state) {
+ for (ProjectState s : state.mParentProjects) {
+ if (!parents.contains(s)) {
+ parents.add(s);
+ addParentProjects(parents, s);
+ }
+ }
+ }
+
+ /**
+ * Update the value of a library dependency.
+ * <p/>This loops on all current dependency looking for the value to replace and then replaces
+ * it.
+ * <p/>This both updates the in-memory {@link #mProperties} values and on-disk
+ * project.properties file.
+ * @param oldValue the old value to replace
+ * @param newValue the new value to set.
+ * @return the status of the replacement. If null, no replacement was done (value not found).
+ */
+ private IStatus replaceLibraryProperty(String oldValue, String newValue) {
+ int index = 1;
+ while (true) {
+ String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++);
+ String rootPath = mProperties.getProperty(propName);
+
+ if (rootPath == null) {
+ break;
+ }
+
+ if (rootPath.equals(oldValue)) {
+ // need to update the properties. Get a working copy to change it and save it on
+ // disk since ProjectProperties is read-only.
+ ProjectPropertiesWorkingCopy workingCopy = mProperties.makeWorkingCopy();
+ workingCopy.setProperty(propName, newValue);
+ try {
+ workingCopy.save();
+
+ // reload the properties with the new values from the disk.
+ mProperties.reload();
+ } catch (Exception e) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, String.format(
+ "Failed to save %1$s for project %2$s",
+ mProperties.getType() .getFilename(), mProject.getName()),
+ e);
+
+ }
+ return Status.OK_STATUS;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Update the full library list, including indirect dependencies. The result is returned by
+ * {@link #getFullLibraryProjects()}.
+ */
+ void updateFullLibraryList() {
+ ArrayList<IProject> list = new ArrayList<IProject>();
+ synchronized (mLibraries) {
+ buildFullLibraryDependencies(mLibraries, list);
+ }
+
+ mLibraryProjects = Collections.unmodifiableList(list);
+ }
+
+ /**
+ * Resolves a given list of libraries, finds out if they depend on other libraries, and
+ * returns a full list of all the direct and indirect dependencies in the proper order (first
+ * is higher priority when calling aapt).
+ * @param inLibraries the libraries to resolve
+ * @param outLibraries where to store all the libraries.
+ */
+ private void buildFullLibraryDependencies(List<LibraryState> inLibraries,
+ ArrayList<IProject> outLibraries) {
+ // loop in the inverse order to resolve dependencies on the libraries, so that if a library
+ // is required by two higher level libraries it can be inserted in the correct place
+ for (int i = inLibraries.size() - 1 ; i >= 0 ; i--) {
+ LibraryState library = inLibraries.get(i);
+
+ // get its libraries if possible
+ ProjectState libProjectState = library.getProjectState();
+ if (libProjectState != null) {
+ List<LibraryState> dependencies = libProjectState.getLibraries();
+
+ // build the dependencies for those libraries
+ buildFullLibraryDependencies(dependencies, outLibraries);
+
+ // and add the current library (if needed) in front (higher priority)
+ if (outLibraries.contains(libProjectState.getProject()) == false) {
+ outLibraries.add(0, libProjectState.getProject());
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Converts a path containing only / by the proper platform separator.
+ */
+ private String convertPath(String path) {
+ return path.replaceAll("/", Matcher.quoteReplacement(File.separator)); //$NON-NLS-1$
+ }
+
+ /**
+ * Normalizes a relative path.
+ */
+ private String normalizePath(String path) {
+ path = convertPath(path);
+ if (path.endsWith("/")) { //$NON-NLS-1$
+ path = path.substring(0, path.length() - 1);
+ }
+ return path;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ProjectState) {
+ return mProject.equals(((ProjectState) obj).mProject);
+ } else if (obj instanceof IProject) {
+ return mProject.equals(obj);
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mProject.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return mProject.getName();
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
new file mode 100644
index 000000000..7ff06fc40
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
@@ -0,0 +1,1620 @@
+/*
+ * 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 static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.EXT_JAR;
+import static com.android.SdkConstants.FD_RES;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ddmlib.IDevice;
+import com.android.ide.common.rendering.LayoutLibrary;
+import com.android.ide.common.sdk.LoadStatus;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.build.DexWrapper;
+import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.project.LibraryClasspathContainerInitializer;
+import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryDifference;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryState;
+import com.android.io.StreamException;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.devices.DeviceManager;
+import com.android.sdklib.internal.avd.AvdManager;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
+import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
+import com.android.sdklib.repository.FullRevision;
+import com.android.utils.ILogger;
+import com.google.common.collect.Maps;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IMarkerDelta;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.ui.IEditorDescriptor;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorReference;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPartSite;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.ide.IDE;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Central point to load, manipulate and deal with the Android SDK. Only one SDK can be used
+ * at the same time.
+ *
+ * To start using an SDK, call {@link #loadSdk(String)} which returns the instance of
+ * the Sdk object.
+ *
+ * To get the list of platforms or add-ons present in the SDK, call {@link #getTargets()}.
+ */
+public final class Sdk {
+ private final static boolean DEBUG = false;
+
+ private final static Object LOCK = new Object();
+
+ private static Sdk sCurrentSdk = null;
+
+ /**
+ * Map associating {@link IProject} and their state {@link ProjectState}.
+ * <p/>This <b>MUST NOT</b> be accessed directly. Instead use {@link #getProjectState(IProject)}.
+ */
+ private final static HashMap<IProject, ProjectState> sProjectStateMap =
+ new HashMap<IProject, ProjectState>();
+
+ /**
+ * Data bundled using during the load of Target data.
+ * <p/>This contains the {@link LoadStatus} and a list of projects that attempted
+ * to compile before the loading was finished. Those projects will be recompiled
+ * at the end of the loading.
+ */
+ private final static class TargetLoadBundle {
+ LoadStatus status;
+ final HashSet<IJavaProject> projectsToReload = new HashSet<IJavaProject>();
+ }
+
+ private final SdkManager mManager;
+ private final Map<String, DexWrapper> mDexWrappers = Maps.newHashMap();
+ private final AvdManager mAvdManager;
+ private final DeviceManager mDeviceManager;
+
+ /** Map associating an {@link IAndroidTarget} to an {@link AndroidTargetData} */
+ private final HashMap<IAndroidTarget, AndroidTargetData> mTargetDataMap =
+ new HashMap<IAndroidTarget, AndroidTargetData>();
+ /** Map associating an {@link IAndroidTarget} and its {@link TargetLoadBundle}. */
+ private final HashMap<IAndroidTarget, TargetLoadBundle> mTargetDataStatusMap =
+ new HashMap<IAndroidTarget, TargetLoadBundle>();
+
+ /**
+ * If true the target data will never load anymore. The only way to reload them is to
+ * completely reload the SDK with {@link #loadSdk(String)}
+ */
+ private boolean mDontLoadTargetData = false;
+
+ private final String mDocBaseUrl;
+
+ /**
+ * Classes implementing this interface will receive notification when targets are changed.
+ */
+ public interface ITargetChangeListener {
+ /**
+ * Sent when project has its target changed.
+ */
+ void onProjectTargetChange(IProject changedProject);
+
+ /**
+ * Called when the targets are loaded (either the SDK finished loading when Eclipse starts,
+ * or the SDK is changed).
+ */
+ void onTargetLoaded(IAndroidTarget target);
+
+ /**
+ * Called when the base content of the SDK is parsed.
+ */
+ void onSdkLoaded();
+ }
+
+ /**
+ * Basic abstract implementation of the ITargetChangeListener for the case where both
+ * {@link #onProjectTargetChange(IProject)} and {@link #onTargetLoaded(IAndroidTarget)}
+ * use the same code based on a simple test requiring to know the current IProject.
+ */
+ public static abstract class TargetChangeListener implements ITargetChangeListener {
+ /**
+ * Returns the {@link IProject} associated with the listener.
+ */
+ public abstract IProject getProject();
+
+ /**
+ * Called when the listener needs to take action on the event. This is only called
+ * if {@link #getProject()} and the {@link IAndroidTarget} associated with the project
+ * match the values received in {@link #onProjectTargetChange(IProject)} and
+ * {@link #onTargetLoaded(IAndroidTarget)}.
+ */
+ public abstract void reload();
+
+ @Override
+ public void onProjectTargetChange(IProject changedProject) {
+ if (changedProject != null && changedProject.equals(getProject())) {
+ reload();
+ }
+ }
+
+ @Override
+ public void onTargetLoaded(IAndroidTarget target) {
+ IProject project = getProject();
+ if (target != null && target.equals(Sdk.getCurrent().getTarget(project))) {
+ reload();
+ }
+ }
+
+ @Override
+ public void onSdkLoaded() {
+ // do nothing;
+ }
+ }
+
+ /**
+ * Returns the lock object used to synchronize all operations dealing with SDK, targets and
+ * projects.
+ */
+ @NonNull
+ public static final Object getLock() {
+ return LOCK;
+ }
+
+ /**
+ * Loads an SDK and returns an {@link Sdk} object if success.
+ * <p/>If the SDK failed to load, it displays an error to the user.
+ * @param sdkLocation the OS path to the SDK.
+ */
+ @Nullable
+ public static Sdk loadSdk(String sdkLocation) {
+ synchronized (LOCK) {
+ if (sCurrentSdk != null) {
+ sCurrentSdk.dispose();
+ sCurrentSdk = null;
+ }
+
+ final AtomicBoolean hasWarning = new AtomicBoolean();
+ final AtomicBoolean hasError = new AtomicBoolean();
+ final ArrayList<String> logMessages = new ArrayList<String>();
+ ILogger log = new ILogger() {
+ @Override
+ public void error(@Nullable Throwable throwable, @Nullable String errorFormat,
+ Object... arg) {
+ hasError.set(true);
+ if (errorFormat != null) {
+ logMessages.add(String.format("Error: " + errorFormat, arg));
+ }
+
+ if (throwable != null) {
+ logMessages.add(throwable.getMessage());
+ }
+ }
+
+ @Override
+ public void warning(@NonNull String warningFormat, Object... arg) {
+ hasWarning.set(true);
+ logMessages.add(String.format("Warning: " + warningFormat, arg));
+ }
+
+ @Override
+ public void info(@NonNull String msgFormat, Object... arg) {
+ logMessages.add(String.format(msgFormat, arg));
+ }
+
+ @Override
+ public void verbose(@NonNull String msgFormat, Object... arg) {
+ info(msgFormat, arg);
+ }
+ };
+
+ // get an SdkManager object for the location
+ SdkManager manager = SdkManager.createManager(sdkLocation, log);
+ try {
+ if (manager == null) {
+ hasError.set(true);
+ } else {
+ // create the AVD Manager
+ AvdManager avdManager = null;
+ try {
+ avdManager = AvdManager.getInstance(manager.getLocalSdk(), log);
+ } catch (AndroidLocationException e) {
+ log.error(e, "Error parsing the AVDs");
+ }
+ sCurrentSdk = new Sdk(manager, avdManager);
+ return sCurrentSdk;
+ }
+ } finally {
+ if (hasError.get() || hasWarning.get()) {
+ StringBuilder sb = new StringBuilder(
+ String.format("%s when loading the SDK:\n",
+ hasError.get() ? "Error" : "Warning"));
+ for (String msg : logMessages) {
+ sb.append('\n');
+ sb.append(msg);
+ }
+ if (hasError.get()) {
+ AdtPlugin.printErrorToConsole("Android SDK", sb.toString());
+ AdtPlugin.displayError("Android SDK", sb.toString());
+ } else {
+ AdtPlugin.printToConsole("Android SDK", sb.toString());
+ }
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Returns the current {@link Sdk} object.
+ */
+ @Nullable
+ public static Sdk getCurrent() {
+ synchronized (LOCK) {
+ return sCurrentSdk;
+ }
+ }
+
+ /**
+ * Returns the location of the current SDK as an OS path string.
+ * Guaranteed to be terminated by a platform-specific path separator.
+ * <p/>
+ * Due to {@link File} canonicalization, this MAY differ from the string used to initialize
+ * the SDK path.
+ *
+ * @return The SDK OS path or null if no SDK is setup.
+ * @deprecated Consider using {@link #getSdkFileLocation()} instead.
+ * @see #getSdkFileLocation()
+ */
+ @Deprecated
+ @Nullable
+ public String getSdkOsLocation() {
+ String path = mManager == null ? null : mManager.getLocation();
+ if (path != null) {
+ // For backward compatibility make sure it ends with a separator.
+ // This used to be the case when the SDK Manager was created from a String path
+ // but now that a File is internally used the trailing dir separator is lost.
+ if (path.length() > 0 && !path.endsWith(File.separator)) {
+ path = path + File.separator;
+ }
+ }
+ return path;
+ }
+
+ /**
+ * Returns the location of the current SDK as a {@link File} or null.
+ *
+ * @return The SDK OS path or null if no SDK is setup.
+ */
+ @Nullable
+ public File getSdkFileLocation() {
+ if (mManager == null || mManager.getLocalSdk() == null) {
+ return null;
+ }
+ return mManager.getLocalSdk().getLocation();
+ }
+
+ /**
+ * Returns a <em>new</em> {@link SdkManager} that can parse the SDK located
+ * at the current {@link #getSdkOsLocation()}.
+ * <p/>
+ * Implementation detail: The {@link Sdk} has its own internal manager with
+ * a custom logger which is not designed to be useful for outsiders. Callers
+ * who need their own {@link SdkManager} for parsing will often want to control
+ * the logger for their own need.
+ * <p/>
+ * This is just a convenient method equivalent to writing:
+ * <pre>SdkManager.createManager(Sdk.getCurrent().getSdkLocation(), log);</pre>
+ *
+ * @param log The logger for the {@link SdkManager}.
+ * @return A new {@link SdkManager} parsing the same location.
+ */
+ public @Nullable SdkManager getNewSdkManager(@NonNull ILogger log) {
+ return SdkManager.createManager(getSdkOsLocation(), log);
+ }
+
+ /**
+ * Returns the URL to the local documentation.
+ * Can return null if no documentation is found in the current SDK.
+ *
+ * @return A file:// URL on the local documentation folder if it exists or null.
+ */
+ @Nullable
+ public String getDocumentationBaseUrl() {
+ return mDocBaseUrl;
+ }
+
+ /**
+ * Returns the list of targets that are available in the SDK.
+ */
+ public IAndroidTarget[] getTargets() {
+ return mManager.getTargets();
+ }
+
+ /**
+ * Queries the underlying SDK Manager to check whether the platforms or addons
+ * directories have changed on-disk. Does not reload the SDK.
+ * <p/>
+ * This is a quick test based on the presence of the directories, their timestamps
+ * and a quick checksum of the source.properties files. It's possible to have
+ * false positives (e.g. if a file is manually modified in a platform) or false
+ * negatives (e.g. if a platform data file is changed manually in a 2nd level
+ * directory without altering the source.properties.)
+ */
+ public boolean haveTargetsChanged() {
+ return mManager.hasChanged();
+ }
+
+ /**
+ * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
+ *
+ * @param hash the {@link IAndroidTarget} hash string.
+ * @return The matching {@link IAndroidTarget} or null.
+ */
+ @Nullable
+ public IAndroidTarget getTargetFromHashString(@NonNull String hash) {
+ return mManager.getTargetFromHashString(hash);
+ }
+
+ @Nullable
+ public BuildToolInfo getBuildToolInfo(@Nullable String buildToolVersion) {
+ if (buildToolVersion != null) {
+ try {
+ return mManager.getBuildTool(FullRevision.parseRevision(buildToolVersion));
+ } catch (Exception e) {
+ // ignore, return null below.
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public BuildToolInfo getLatestBuildTool() {
+ return mManager.getLatestBuildTool();
+ }
+
+ /**
+ * Initializes a new project with a target. This creates the <code>project.properties</code>
+ * file.
+ * @param project the project to initialize
+ * @param target the project's target.
+ * @throws IOException if creating the file failed in any way.
+ * @throws StreamException if processing the project property file fails
+ */
+ public void initProject(@Nullable IProject project, @Nullable IAndroidTarget target)
+ throws IOException, StreamException {
+ if (project == null || target == null) {
+ return;
+ }
+
+ synchronized (LOCK) {
+ // check if there's already a state?
+ ProjectState state = getProjectState(project);
+
+ ProjectPropertiesWorkingCopy properties = null;
+
+ if (state != null) {
+ properties = state.getProperties().makeWorkingCopy();
+ }
+
+ if (properties == null) {
+ IPath location = project.getLocation();
+ if (location == null) { // can return null when the project is being deleted.
+ // do nothing and return null;
+ return;
+ }
+
+ properties = ProjectProperties.create(location.toOSString(), PropertyType.PROJECT);
+ }
+
+ // save the target hash string in the project persistent property
+ properties.setProperty(ProjectProperties.PROPERTY_TARGET, target.hashString());
+ properties.save();
+ }
+ }
+
+ /**
+ * Returns the {@link ProjectState} object associated with a given project.
+ * <p/>
+ * This method is the only way to properly get the project's {@link ProjectState}
+ * If the project has not yet been loaded, then it is loaded.
+ * <p/>Because this methods deals with projects, it's not linked to an actual {@link Sdk}
+ * objects, and therefore is static.
+ * <p/>The value returned by {@link ProjectState#getTarget()} will change as {@link Sdk} objects
+ * are replaced.
+ * @param project the request project
+ * @return the ProjectState for the project.
+ */
+ @Nullable
+ @SuppressWarnings("deprecation")
+ public static ProjectState getProjectState(IProject project) {
+ if (project == null) {
+ return null;
+ }
+
+ synchronized (LOCK) {
+ ProjectState state = sProjectStateMap.get(project);
+ if (state == null) {
+ // load the project.properties from the project folder.
+ IPath location = project.getLocation();
+ if (location == null) { // can return null when the project is being deleted.
+ // do nothing and return null;
+ return null;
+ }
+
+ String projectLocation = location.toOSString();
+
+ ProjectProperties properties = ProjectProperties.load(projectLocation,
+ PropertyType.PROJECT);
+ if (properties == null) {
+ // legacy support: look for default.properties and rename it if needed.
+ properties = ProjectProperties.load(projectLocation,
+ PropertyType.LEGACY_DEFAULT);
+
+ if (properties == null) {
+ AdtPlugin.log(IStatus.ERROR,
+ "Failed to load properties file for project '%s'",
+ project.getName());
+ return null;
+ } else {
+ //legacy mode.
+ // get a working copy with the new type "project"
+ ProjectPropertiesWorkingCopy wc = properties.makeWorkingCopy(
+ PropertyType.PROJECT);
+ // and save it
+ try {
+ wc.save();
+
+ // delete the old file.
+ ProjectProperties.delete(projectLocation, PropertyType.LEGACY_DEFAULT);
+
+ // make sure to use the new properties
+ properties = ProjectProperties.load(projectLocation,
+ PropertyType.PROJECT);
+ } catch (Exception e) {
+ AdtPlugin.log(IStatus.ERROR,
+ "Failed to rename properties file to %1$s for project '%s2$'",
+ PropertyType.PROJECT.getFilename(), project.getName());
+ }
+ }
+ }
+
+ state = new ProjectState(project, properties);
+ sProjectStateMap.put(project, state);
+
+ // try to resolve the target
+ if (AdtPlugin.getDefault().getSdkLoadStatus() == LoadStatus.LOADED) {
+ sCurrentSdk.loadTargetAndBuildTools(state);
+ }
+ }
+
+ return state;
+ }
+ }
+
+ /**
+ * Returns the {@link IAndroidTarget} object associated with the given {@link IProject}.
+ */
+ @Nullable
+ public IAndroidTarget getTarget(IProject project) {
+ if (project == null) {
+ return null;
+ }
+
+ ProjectState state = getProjectState(project);
+ if (state != null) {
+ return state.getTarget();
+ }
+
+ return null;
+ }
+
+ /**
+ * Loads the {@link IAndroidTarget} and BuildTools for a given project.
+ * <p/>This method will get the target hash string from the project properties, and resolve
+ * it to an {@link IAndroidTarget} object and store it inside the {@link ProjectState}.
+ * @param state the state representing the project to load.
+ * @return the target that was loaded.
+ */
+ @Nullable
+ public IAndroidTarget loadTargetAndBuildTools(ProjectState state) {
+ IAndroidTarget target = null;
+ if (state != null) {
+ String hash = state.getTargetHashString();
+ if (hash != null) {
+ state.setTarget(target = getTargetFromHashString(hash));
+ }
+
+ String markerMessage = null;
+ String buildToolInfoVersion = state.getBuildToolInfoVersion();
+ if (buildToolInfoVersion != null) {
+ BuildToolInfo buildToolsInfo = getBuildToolInfo(buildToolInfoVersion);
+
+ if (buildToolsInfo != null) {
+ state.setBuildToolInfo(buildToolsInfo);
+ } else {
+ markerMessage = String.format("Unable to resolve %s property value '%s'",
+ ProjectProperties.PROPERTY_BUILD_TOOLS,
+ buildToolInfoVersion);
+ }
+ } else {
+ // this is ok, we'll use the latest one automatically.
+ state.setBuildToolInfo(null);
+ }
+
+ handleBuildToolsMarker(state.getProject(), markerMessage);
+ }
+
+ return target;
+ }
+
+ /**
+ * Adds or edit a build tools marker from the given project. This is done through a Job.
+ * @param project the project
+ * @param markerMessage the message. if null the marker is removed.
+ */
+ private void handleBuildToolsMarker(final IProject project, final String markerMessage) {
+ Job markerJob = new Job("Android SDK: Build Tools Marker") {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ if (project.isAccessible()) {
+ // always delete existing marker first
+ project.deleteMarkers(AdtConstants.MARKER_BUILD_TOOLS, true,
+ IResource.DEPTH_ZERO);
+
+ // add the new one if needed.
+ if (markerMessage != null) {
+ BaseProjectHelper.markProject(project,
+ AdtConstants.MARKER_BUILD_TOOLS,
+ markerMessage, IMarker.SEVERITY_ERROR,
+ IMarker.PRIORITY_HIGH);
+ }
+ }
+ } catch (CoreException e2) {
+ AdtPlugin.log(e2, null);
+ // Don't return e2.getStatus(); the job control will then produce
+ // a popup with this error, which isn't very interesting for the
+ // user.
+ }
+
+ return Status.OK_STATUS;
+ }
+ };
+
+ // build jobs are run after other interactive jobs
+ markerJob.setPriority(Job.BUILD);
+ markerJob.setRule(ResourcesPlugin.getWorkspace().getRoot());
+ markerJob.schedule();
+ }
+
+ /**
+ * Checks and loads (if needed) the data for a given target.
+ * <p/> The data is loaded in a separate {@link Job}, and opened editors will be notified
+ * through their implementation of {@link ITargetChangeListener#onTargetLoaded(IAndroidTarget)}.
+ * <p/>An optional project as second parameter can be given to be recompiled once the target
+ * data is finished loading.
+ * <p/>The return value is non-null only if the target data has already been loaded (and in this
+ * case is the status of the load operation)
+ * @param target the target to load.
+ * @param project an optional project to be recompiled when the target data is loaded.
+ * If the target is already loaded, nothing happens.
+ * @return The load status if the target data is already loaded.
+ */
+ @NonNull
+ public LoadStatus checkAndLoadTargetData(final IAndroidTarget target, IJavaProject project) {
+ boolean loadData = false;
+
+ synchronized (LOCK) {
+ if (mDontLoadTargetData) {
+ return LoadStatus.FAILED;
+ }
+
+ TargetLoadBundle bundle = mTargetDataStatusMap.get(target);
+ if (bundle == null) {
+ bundle = new TargetLoadBundle();
+ mTargetDataStatusMap.put(target,bundle);
+
+ // set status to loading
+ bundle.status = LoadStatus.LOADING;
+
+ // add project to bundle
+ if (project != null) {
+ bundle.projectsToReload.add(project);
+ }
+
+ // and set the flag to start the loading below
+ loadData = true;
+ } else if (bundle.status == LoadStatus.LOADING) {
+ // add project to bundle
+ if (project != null) {
+ bundle.projectsToReload.add(project);
+ }
+
+ return bundle.status;
+ } else if (bundle.status == LoadStatus.LOADED || bundle.status == LoadStatus.FAILED) {
+ return bundle.status;
+ }
+ }
+
+ if (loadData) {
+ Job job = new Job(String.format("Loading data for %1$s", target.getFullName())) {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ AdtPlugin plugin = AdtPlugin.getDefault();
+ try {
+ IStatus status = new AndroidTargetParser(target).run(monitor);
+
+ IJavaProject[] javaProjectArray = null;
+
+ synchronized (LOCK) {
+ TargetLoadBundle bundle = mTargetDataStatusMap.get(target);
+
+ if (status.getCode() != IStatus.OK) {
+ bundle.status = LoadStatus.FAILED;
+ bundle.projectsToReload.clear();
+ } else {
+ bundle.status = LoadStatus.LOADED;
+
+ // Prepare the array of project to recompile.
+ // The call is done outside of the synchronized block.
+ javaProjectArray = bundle.projectsToReload.toArray(
+ new IJavaProject[bundle.projectsToReload.size()]);
+
+ // and update the UI of the editors that depend on the target data.
+ plugin.updateTargetListeners(target);
+ }
+ }
+
+ if (javaProjectArray != null) {
+ ProjectHelper.updateProjects(javaProjectArray);
+ }
+
+ return status;
+ } catch (Throwable t) {
+ synchronized (LOCK) {
+ TargetLoadBundle bundle = mTargetDataStatusMap.get(target);
+ bundle.status = LoadStatus.FAILED;
+ }
+
+ AdtPlugin.log(t, "Exception in checkAndLoadTargetData."); //$NON-NLS-1$
+ String message = String.format("Parsing Data for %1$s failed", target.hashString());
+ if (t instanceof UnsupportedClassVersionError) {
+ message = "To use this platform, run Eclipse with JDK 7 or later. (" + message + ")";
+ }
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message, t);
+ }
+ }
+ };
+ job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs
+ job.setRule(ResourcesPlugin.getWorkspace().getRoot());
+ job.schedule();
+ }
+
+ // The only way to go through here is when the loading starts through the Job.
+ // Therefore the current status of the target is LOADING.
+ return LoadStatus.LOADING;
+ }
+
+ /**
+ * Return the {@link AndroidTargetData} for a given {@link IAndroidTarget}.
+ */
+ @Nullable
+ public AndroidTargetData getTargetData(IAndroidTarget target) {
+ synchronized (LOCK) {
+ return mTargetDataMap.get(target);
+ }
+ }
+
+ /**
+ * Return the {@link AndroidTargetData} for a given {@link IProject}.
+ */
+ @Nullable
+ public AndroidTargetData getTargetData(IProject project) {
+ synchronized (LOCK) {
+ IAndroidTarget target = getTarget(project);
+ if (target != null) {
+ return getTargetData(target);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a {@link DexWrapper} object to be used to execute dx commands. If dx.jar was not
+ * loaded properly, then this will return <code>null</code>.
+ */
+ @Nullable
+ public DexWrapper getDexWrapper(@Nullable BuildToolInfo buildToolInfo) {
+ if (buildToolInfo == null) {
+ return null;
+ }
+ synchronized (LOCK) {
+ String dexLocation = buildToolInfo.getPath(BuildToolInfo.PathId.DX_JAR);
+ DexWrapper dexWrapper = mDexWrappers.get(dexLocation);
+
+ if (dexWrapper == null) {
+ // load DX.
+ dexWrapper = new DexWrapper();
+ IStatus res = dexWrapper.loadDex(dexLocation);
+ if (res != Status.OK_STATUS) {
+ AdtPlugin.log(null, res.getMessage());
+ dexWrapper = null;
+ } else {
+ mDexWrappers.put(dexLocation, dexWrapper);
+ }
+ }
+
+ return dexWrapper;
+ }
+ }
+
+ public void unloadDexWrappers() {
+ synchronized (LOCK) {
+ for (DexWrapper wrapper : mDexWrappers.values()) {
+ wrapper.unload();
+ }
+ mDexWrappers.clear();
+ }
+ }
+
+ /**
+ * Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could
+ * be <code>null</code>.
+ */
+ @Nullable
+ public AvdManager getAvdManager() {
+ return mAvdManager;
+ }
+
+ @Nullable
+ public static AndroidVersion getDeviceVersion(@NonNull IDevice device) {
+ try {
+ Map<String, String> props = device.getProperties();
+ String apiLevel = props.get(IDevice.PROP_BUILD_API_LEVEL);
+ if (apiLevel == null) {
+ return null;
+ }
+
+ return new AndroidVersion(Integer.parseInt(apiLevel),
+ props.get((IDevice.PROP_BUILD_CODENAME)));
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ @NonNull
+ public DeviceManager getDeviceManager() {
+ return mDeviceManager;
+ }
+
+ /**
+ * Returns a list of {@link ProjectState} representing projects depending, directly or
+ * indirectly on a given library project.
+ * @param project the library project.
+ * @return a possibly empty list of ProjectState.
+ */
+ @NonNull
+ public static Set<ProjectState> getMainProjectsFor(IProject project) {
+ synchronized (LOCK) {
+ // first get the project directly depending on this.
+ Set<ProjectState> list = new HashSet<ProjectState>();
+
+ // loop on all project and see if ProjectState.getLibrary returns a non null
+ // project.
+ for (Entry<IProject, ProjectState> entry : sProjectStateMap.entrySet()) {
+ if (project != entry.getKey()) {
+ LibraryState library = entry.getValue().getLibrary(project);
+ if (library != null) {
+ list.add(entry.getValue());
+ }
+ }
+ }
+
+ // now look for projects depending on the projects directly depending on the library.
+ HashSet<ProjectState> result = new HashSet<ProjectState>(list);
+ for (ProjectState p : list) {
+ if (p.isLibrary()) {
+ Set<ProjectState> set = getMainProjectsFor(p.getProject());
+ result.addAll(set);
+ }
+ }
+
+ return result;
+ }
+ }
+
+ /**
+ * Unload the SDK's target data.
+ *
+ * If <var>preventReload</var>, this effect is final until the SDK instance is changed
+ * through {@link #loadSdk(String)}.
+ *
+ * The goal is to unload the targets to be able to replace existing targets with new ones,
+ * before calling {@link #loadSdk(String)} to fully reload the SDK.
+ *
+ * @param preventReload prevent the data from being loaded again for the remaining live of
+ * this {@link Sdk} instance.
+ */
+ public void unloadTargetData(boolean preventReload) {
+ synchronized (LOCK) {
+ mDontLoadTargetData = preventReload;
+
+ // dispose of the target data.
+ for (AndroidTargetData data : mTargetDataMap.values()) {
+ data.dispose();
+ }
+
+ mTargetDataMap.clear();
+ }
+ }
+
+ private Sdk(SdkManager manager, AvdManager avdManager) {
+ mManager = manager;
+ mAvdManager = avdManager;
+
+ // listen to projects closing
+ GlobalProjectMonitor monitor = GlobalProjectMonitor.getMonitor();
+ // need to register the resource event listener first because the project listener
+ // is called back during registration with project opened in the workspace.
+ monitor.addResourceEventListener(mResourceEventListener);
+ monitor.addProjectListener(mProjectListener);
+ monitor.addFileListener(mFileListener,
+ IResourceDelta.CHANGED | IResourceDelta.ADDED | IResourceDelta.REMOVED);
+
+ // pre-compute some paths
+ mDocBaseUrl = getDocumentationBaseUrl(manager.getLocation() +
+ SdkConstants.OS_SDK_DOCS_FOLDER);
+
+ mDeviceManager = DeviceManager.createInstance(manager.getLocalSdk().getLocation(),
+ AdtPlugin.getDefault());
+
+ // update whatever ProjectState is already present with new IAndroidTarget objects.
+ synchronized (LOCK) {
+ for (Entry<IProject, ProjectState> entry: sProjectStateMap.entrySet()) {
+ loadTargetAndBuildTools(entry.getValue());
+ }
+ }
+ }
+
+ /**
+ * Cleans and unloads the SDK.
+ */
+ private void dispose() {
+ GlobalProjectMonitor monitor = GlobalProjectMonitor.getMonitor();
+ monitor.removeProjectListener(mProjectListener);
+ monitor.removeFileListener(mFileListener);
+ monitor.removeResourceEventListener(mResourceEventListener);
+
+ // the IAndroidTarget objects are now obsolete so update the project states.
+ synchronized (LOCK) {
+ for (Entry<IProject, ProjectState> entry: sProjectStateMap.entrySet()) {
+ entry.getValue().setTarget(null);
+ }
+
+ // dispose of the target data.
+ for (AndroidTargetData data : mTargetDataMap.values()) {
+ data.dispose();
+ }
+
+ mTargetDataMap.clear();
+ }
+ }
+
+ void setTargetData(IAndroidTarget target, AndroidTargetData data) {
+ synchronized (LOCK) {
+ mTargetDataMap.put(target, data);
+ }
+ }
+
+ /**
+ * Returns the URL to the local documentation.
+ * Can return null if no documentation is found in the current SDK.
+ *
+ * @param osDocsPath Path to the documentation folder in the current SDK.
+ * The folder may not actually exist.
+ * @return A file:// URL on the local documentation folder if it exists or null.
+ */
+ private String getDocumentationBaseUrl(String osDocsPath) {
+ File f = new File(osDocsPath);
+
+ if (f.isDirectory()) {
+ try {
+ // Note: to create a file:// URL, one would typically use something like
+ // f.toURI().toURL().toString(). However this generates a broken path on
+ // Windows, namely "C:\\foo" is converted to "file:/C:/foo" instead of
+ // "file:///C:/foo" (i.e. there should be 3 / after "file:"). So we'll
+ // do the correct thing manually.
+
+ String path = f.getAbsolutePath();
+ if (File.separatorChar != '/') {
+ path = path.replace(File.separatorChar, '/');
+ }
+
+ // For some reason the URL class doesn't add the mandatory "//" after
+ // the "file:" protocol name, so it has to be hacked into the path.
+ URL url = new URL("file", null, "//" + path); //$NON-NLS-1$ //$NON-NLS-2$
+ String result = url.toString();
+ return result;
+ } catch (MalformedURLException e) {
+ // ignore malformed URLs
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Delegate listener for project changes.
+ */
+ private IProjectListener mProjectListener = new IProjectListener() {
+ @Override
+ public void projectClosed(IProject project) {
+ onProjectRemoved(project, false /*deleted*/);
+ }
+
+ @Override
+ public void projectDeleted(IProject project) {
+ onProjectRemoved(project, true /*deleted*/);
+ }
+
+ private void onProjectRemoved(IProject removedProject, boolean deleted) {
+ if (DEBUG) {
+ System.out.println(">>> CLOSED: " + removedProject.getName());
+ }
+
+ // get the target project
+ synchronized (LOCK) {
+ // Don't use getProject() as it could create the ProjectState if it's not
+ // there yet and this is not what we want. We want the current object.
+ // Therefore, direct access to the map.
+ ProjectState removedState = sProjectStateMap.get(removedProject);
+ if (removedState != null) {
+ // 1. clear the layout lib cache associated with this project
+ IAndroidTarget target = removedState.getTarget();
+ if (target != null) {
+ // get the bridge for the target, and clear the cache for this project.
+ AndroidTargetData data = mTargetDataMap.get(target);
+ if (data != null) {
+ LayoutLibrary layoutLib = data.getLayoutLibrary();
+ if (layoutLib != null && layoutLib.getStatus() == LoadStatus.LOADED) {
+ layoutLib.clearCaches(removedProject);
+ }
+ }
+ }
+
+ // 2. if the project is a library, make sure to update the
+ // LibraryState for any project referencing it.
+ // Also, record the updated projects that are libraries, to update
+ // projects that depend on them.
+ for (ProjectState projectState : sProjectStateMap.values()) {
+ LibraryState libState = projectState.getLibrary(removedProject);
+ if (libState != null) {
+ // Close the library right away.
+ // This remove links between the LibraryState and the projectState.
+ // This is because in case of a rename of a project, projectClosed and
+ // projectOpened will be called before any other job is run, so we
+ // need to make sure projectOpened is closed with the main project
+ // state up to date.
+ libState.close();
+
+ // record that this project changed, and in case it's a library
+ // that its parents need to be updated as well.
+ markProject(projectState, projectState.isLibrary());
+ }
+ }
+
+ // now remove the project for the project map.
+ sProjectStateMap.remove(removedProject);
+ }
+ }
+
+ if (DEBUG) {
+ System.out.println("<<<");
+ }
+ }
+
+ @Override
+ public void projectOpened(IProject project) {
+ onProjectOpened(project);
+ }
+
+ @Override
+ public void projectOpenedWithWorkspace(IProject project) {
+ // no need to force recompilation when projects are opened with the workspace.
+ onProjectOpened(project);
+ }
+
+ @Override
+ public void allProjectsOpenedWithWorkspace() {
+ // Correct currently open editors
+ fixOpenLegacyEditors();
+ }
+
+ private void onProjectOpened(final IProject openedProject) {
+
+ ProjectState openedState = getProjectState(openedProject);
+ if (openedState != null) {
+ if (DEBUG) {
+ System.out.println(">>> OPENED: " + openedProject.getName());
+ }
+
+ synchronized (LOCK) {
+ final boolean isLibrary = openedState.isLibrary();
+ final boolean hasLibraries = openedState.hasLibraries();
+
+ if (isLibrary || hasLibraries) {
+ boolean foundLibraries = false;
+ // loop on all the existing project and update them based on this new
+ // project
+ for (ProjectState projectState : sProjectStateMap.values()) {
+ if (projectState != openedState) {
+ // If the project has libraries, check if this project
+ // is a reference.
+ if (hasLibraries) {
+ // ProjectState#needs() both checks if this is a missing library
+ // and updates LibraryState to contains the new values.
+ // This must always be called.
+ LibraryState libState = openedState.needs(projectState);
+
+ if (libState != null) {
+ // found a library! Add the main project to the list of
+ // modified project
+ foundLibraries = true;
+ }
+ }
+
+ // if the project is a library check if the other project depend
+ // on it.
+ if (isLibrary) {
+ // ProjectState#needs() both checks if this is a missing library
+ // and updates LibraryState to contains the new values.
+ // This must always be called.
+ LibraryState libState = projectState.needs(openedState);
+
+ if (libState != null) {
+ // There's a dependency! Add the project to the list of
+ // modified project, but also to a list of projects
+ // that saw one of its dependencies resolved.
+ markProject(projectState, projectState.isLibrary());
+ }
+ }
+ }
+ }
+
+ // if the project has a libraries and we found at least one, we add
+ // the project to the list of modified project.
+ // Since we already went through the parent, no need to update them.
+ if (foundLibraries) {
+ markProject(openedState, false /*updateParents*/);
+ }
+ }
+ }
+
+ // Correct file editor associations.
+ fixEditorAssociations(openedProject);
+
+ // Fix classpath entries in a job since the workspace might be locked now.
+ Job fixCpeJob = new Job("Adjusting Android Project Classpath") {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ ProjectHelper.fixProjectClasspathEntries(
+ JavaCore.create(openedProject));
+ } catch (JavaModelException e) {
+ AdtPlugin.log(e, "error fixing classpath entries");
+ // Don't return e2.getStatus(); the job control will then produce
+ // a popup with this error, which isn't very interesting for the
+ // user.
+ }
+
+ return Status.OK_STATUS;
+ }
+ };
+
+ // build jobs are run after other interactive jobs
+ fixCpeJob.setPriority(Job.BUILD);
+ fixCpeJob.setRule(ResourcesPlugin.getWorkspace().getRoot());
+ fixCpeJob.schedule();
+
+
+ if (DEBUG) {
+ System.out.println("<<<");
+ }
+ }
+ }
+
+ @Override
+ public void projectRenamed(IProject project, IPath from) {
+ // we don't actually care about this anymore.
+ }
+ };
+
+ /**
+ * Delegate listener for file changes.
+ */
+ private IFileListener mFileListener = new IFileListener() {
+ @Override
+ public void fileChanged(final @NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas,
+ int kind, @Nullable String extension, int flags, boolean isAndroidPRoject) {
+ if (!isAndroidPRoject) {
+ return;
+ }
+
+ if (SdkConstants.FN_PROJECT_PROPERTIES.equals(file.getName()) &&
+ file.getParent() == file.getProject()) {
+ try {
+ // reload the content of the project.properties file and update
+ // the target.
+ IProject iProject = file.getProject();
+
+ ProjectState state = Sdk.getProjectState(iProject);
+
+ // get the current target and build tools
+ IAndroidTarget oldTarget = state.getTarget();
+ boolean oldRsSupportMode = state.getRenderScriptSupportMode();
+
+ // get the current library flag
+ boolean wasLibrary = state.isLibrary();
+
+ LibraryDifference diff = state.reloadProperties();
+
+ // load the (possibly new) target.
+ IAndroidTarget newTarget = loadTargetAndBuildTools(state);
+
+ // reload the libraries if needed
+ if (diff.hasDiff()) {
+ if (diff.added) {
+ synchronized (LOCK) {
+ for (ProjectState projectState : sProjectStateMap.values()) {
+ if (projectState != state) {
+ // need to call needs to do the libraryState link,
+ // but no need to look at the result, as we'll compare
+ // the result of getFullLibraryProjects()
+ // this is easier to due to indirect dependencies.
+ state.needs(projectState);
+ }
+ }
+ }
+ }
+
+ markProject(state, wasLibrary || state.isLibrary());
+ }
+
+ // apply the new target if needed.
+ if (newTarget != oldTarget ||
+ oldRsSupportMode != state.getRenderScriptSupportMode()) {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(
+ file.getProject());
+ if (javaProject != null) {
+ ProjectHelper.updateProject(javaProject);
+ }
+
+ // update the editors to reload with the new target
+ AdtPlugin.getDefault().updateTargetListeners(iProject);
+ }
+ } catch (CoreException e) {
+ // This can't happen as it's only for closed project (or non existing)
+ // but in that case we can't get a fileChanged on this file.
+ }
+ } else if (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED) {
+ // check if it's an add/remove on a jar files inside libs
+ if (EXT_JAR.equals(extension) &&
+ file.getProjectRelativePath().segmentCount() == 2 &&
+ file.getParent().getName().equals(SdkConstants.FD_NATIVE_LIBS)) {
+ // need to update the project and whatever depend on it.
+
+ processJarFileChange(file);
+ }
+ }
+ }
+
+ private void processJarFileChange(final IFile file) {
+ try {
+ IProject iProject = file.getProject();
+
+ if (iProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
+ return;
+ }
+
+ List<IJavaProject> projectList = new ArrayList<IJavaProject>();
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(iProject);
+ if (javaProject != null) {
+ projectList.add(javaProject);
+ }
+
+ ProjectState state = Sdk.getProjectState(iProject);
+
+ if (state != null) {
+ Collection<ProjectState> parents = state.getFullParentProjects();
+ for (ProjectState s : parents) {
+ javaProject = BaseProjectHelper.getJavaProject(s.getProject());
+ if (javaProject != null) {
+ projectList.add(javaProject);
+ }
+ }
+
+ ProjectHelper.updateProjects(
+ projectList.toArray(new IJavaProject[projectList.size()]));
+ }
+ } catch (CoreException e) {
+ // This can't happen as it's only for closed project (or non existing)
+ // but in that case we can't get a fileChanged on this file.
+ }
+ }
+ };
+
+ /** List of modified projects. This is filled in
+ * {@link IProjectListener#projectOpened(IProject)},
+ * {@link IProjectListener#projectOpenedWithWorkspace(IProject)},
+ * {@link IProjectListener#projectClosed(IProject)}, and
+ * {@link IProjectListener#projectDeleted(IProject)} and processed in
+ * {@link IResourceEventListener#resourceChangeEventEnd()}.
+ */
+ private final List<ProjectState> mModifiedProjects = new ArrayList<ProjectState>();
+ private final List<ProjectState> mModifiedChildProjects = new ArrayList<ProjectState>();
+
+ private void markProject(ProjectState projectState, boolean updateParents) {
+ if (mModifiedProjects.contains(projectState) == false) {
+ if (DEBUG) {
+ System.out.println("\tMARKED: " + projectState.getProject().getName());
+ }
+ mModifiedProjects.add(projectState);
+ }
+
+ // if the project is resolved also add it to this list.
+ if (updateParents) {
+ if (mModifiedChildProjects.contains(projectState) == false) {
+ if (DEBUG) {
+ System.out.println("\tMARKED(child): " + projectState.getProject().getName());
+ }
+ mModifiedChildProjects.add(projectState);
+ }
+ }
+ }
+
+ /**
+ * Delegate listener for resource changes. This is called before and after any calls to the
+ * project and file listeners (for a given resource change event).
+ */
+ private IResourceEventListener mResourceEventListener = new IResourceEventListener() {
+ @Override
+ public void resourceChangeEventStart() {
+ mModifiedProjects.clear();
+ mModifiedChildProjects.clear();
+ }
+
+ @Override
+ public void resourceChangeEventEnd() {
+ if (mModifiedProjects.size() == 0) {
+ return;
+ }
+
+ // first make sure all the parents are updated
+ updateParentProjects();
+
+ // for all modified projects, update their library list
+ // and gather their IProject
+ final List<IJavaProject> projectList = new ArrayList<IJavaProject>();
+ for (ProjectState state : mModifiedProjects) {
+ state.updateFullLibraryList();
+ projectList.add(JavaCore.create(state.getProject()));
+ }
+
+ Job job = new Job("Android Library Update") { //$NON-NLS-1$
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ LibraryClasspathContainerInitializer.updateProjects(
+ projectList.toArray(new IJavaProject[projectList.size()]));
+
+ for (IJavaProject javaProject : projectList) {
+ try {
+ javaProject.getProject().build(IncrementalProjectBuilder.FULL_BUILD,
+ monitor);
+ } catch (CoreException e) {
+ // pass
+ }
+ }
+ return Status.OK_STATUS;
+ }
+ };
+ job.setPriority(Job.BUILD);
+ job.setRule(ResourcesPlugin.getWorkspace().getRoot());
+ job.schedule();
+ }
+ };
+
+ /**
+ * Updates all existing projects with a given list of new/updated libraries.
+ * This loops through all opened projects and check if they depend on any of the given
+ * library project, and if they do, they are linked together.
+ */
+ private void updateParentProjects() {
+ if (mModifiedChildProjects.size() == 0) {
+ return;
+ }
+
+ ArrayList<ProjectState> childProjects = new ArrayList<ProjectState>(mModifiedChildProjects);
+ mModifiedChildProjects.clear();
+ synchronized (LOCK) {
+ // for each project for which we must update its parent, we loop on the parent
+ // projects and adds them to the list of modified projects. If they are themselves
+ // libraries, we add them too.
+ for (ProjectState state : childProjects) {
+ if (DEBUG) {
+ System.out.println(">>> Updating parents of " + state.getProject().getName());
+ }
+ List<ProjectState> parents = state.getParentProjects();
+ for (ProjectState parent : parents) {
+ markProject(parent, parent.isLibrary());
+ }
+ if (DEBUG) {
+ System.out.println("<<<");
+ }
+ }
+ }
+
+ // done, but there may be parents that are also libraries. Need to update their parents.
+ updateParentProjects();
+ }
+
+ /**
+ * Fix editor associations for the given project, if not already done.
+ * <p/>
+ * Eclipse has a per-file setting for which editor should be used for each file
+ * (see {@link IDE#setDefaultEditor(IFile, String)}).
+ * We're using this flag to pick between the various XML editors (layout, drawable, etc)
+ * since they all have the same file name extension.
+ * <p/>
+ * Unfortunately, the file setting can be "wrong" for two reasons:
+ * <ol>
+ * <li> The editor type was added <b>after</b> a file had been seen by the IDE.
+ * For example, we added new editors for animations and for drawables around
+ * ADT 12, but any file seen by ADT in earlier versions will continue to use
+ * the vanilla Eclipse XML editor instead.
+ * <li> A bug in ADT 14 and ADT 15 (see issue 21124) meant that files created in new
+ * folders would end up with wrong editor associations. Even though that bug
+ * is fixed in ADT 16, the fix only affects new files, it cannot retroactively
+ * fix editor associations that were set incorrectly by ADT 14 or 15.
+ * </ol>
+ * <p/>
+ * This method attempts to fix the editor bindings retroactively by scanning all the
+ * resource XML files and resetting the editor associations.
+ * Since this is a potentially slow operation, this is only done "once"; we use a
+ * persistent project property to avoid looking repeatedly. In the future if we add
+ * additional editors, we can rev the scanned version value.
+ */
+ private void fixEditorAssociations(final IProject project) {
+ QualifiedName KEY = new QualifiedName(AdtPlugin.PLUGIN_ID, "editorbinding"); //$NON-NLS-1$
+
+ try {
+ String value = project.getPersistentProperty(KEY);
+ int currentVersion = 0;
+ if (value != null) {
+ try {
+ currentVersion = Integer.parseInt(value);
+ } catch (Exception ingore) {
+ }
+ }
+
+ // The target version we're comparing to. This must be incremented each time
+ // we change the processing here so that a new version of the plugin would
+ // try to fix existing user projects.
+ final int targetVersion = 2;
+
+ if (currentVersion >= targetVersion) {
+ return;
+ }
+
+ // Set to specific version such that we can rev the version in the future
+ // to trigger further scanning
+ project.setPersistentProperty(KEY, Integer.toString(targetVersion));
+
+ // Now update the actual editor associations.
+ Job job = new Job("Update Android editor bindings") { //$NON-NLS-1$
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ for (IResource folderResource : project.getFolder(FD_RES).members()) {
+ if (folderResource instanceof IFolder) {
+ IFolder folder = (IFolder) folderResource;
+
+ for (IResource resource : folder.members()) {
+ if (resource instanceof IFile &&
+ resource.getName().endsWith(DOT_XML)) {
+ fixXmlFile((IFile) resource);
+ }
+ }
+ }
+ }
+
+ // TODO change AndroidManifest.xml ID too
+
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return Status.OK_STATUS;
+ }
+
+ /**
+ * Attempt to fix the editor ID for the given /res XML file.
+ */
+ private void fixXmlFile(final IFile file) {
+ // Fix the default editor ID for this resource.
+ // This has no effect on currently open editors.
+ IEditorDescriptor desc = IDE.getDefaultEditor(file);
+
+ if (desc == null || !CommonXmlEditor.ID.equals(desc.getId())) {
+ IDE.setDefaultEditor(file, CommonXmlEditor.ID);
+ }
+ }
+ };
+ job.setPriority(Job.BUILD);
+ job.schedule();
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ /**
+ * Tries to fix all currently open Android legacy editors.
+ * <p/>
+ * If an editor is found to match one of the legacy ids, we'll try to close it.
+ * If that succeeds, we try to reopen it using the new common editor ID.
+ * <p/>
+ * This method must be run from the UI thread.
+ */
+ private void fixOpenLegacyEditors() {
+
+ AdtPlugin adt = AdtPlugin.getDefault();
+ if (adt == null) {
+ return;
+ }
+
+ final IPreferenceStore store = adt.getPreferenceStore();
+ int currentValue = store.getInt(AdtPrefs.PREFS_FIX_LEGACY_EDITORS);
+ // The target version we're comparing to. This must be incremented each time
+ // we change the processing here so that a new version of the plugin would
+ // try to fix existing editors.
+ final int targetValue = 1;
+
+ if (currentValue >= targetValue) {
+ return;
+ }
+
+ // To be able to close and open editors we need to make sure this is done
+ // in the UI thread, which this isn't invoked from.
+ PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ HashSet<String> legacyIds =
+ new HashSet<String>(Arrays.asList(CommonXmlEditor.LEGACY_EDITOR_IDS));
+
+ for (IWorkbenchWindow win : PlatformUI.getWorkbench().getWorkbenchWindows()) {
+ for (IWorkbenchPage page : win.getPages()) {
+ for (IEditorReference ref : page.getEditorReferences()) {
+ try {
+ IEditorInput input = ref.getEditorInput();
+ if (input instanceof IFileEditorInput) {
+ IFile file = ((IFileEditorInput)input).getFile();
+ IEditorPart part = ref.getEditor(true /*restore*/);
+ if (part != null) {
+ IWorkbenchPartSite site = part.getSite();
+ if (site != null) {
+ String id = site.getId();
+ if (legacyIds.contains(id)) {
+ // This editor matches one of legacy editor IDs.
+ fixEditor(page, part, input, file, id);
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ // Remember that we managed to do fix all editors
+ store.setValue(AdtPrefs.PREFS_FIX_LEGACY_EDITORS, targetValue);
+ }
+
+ private void fixEditor(
+ IWorkbenchPage page,
+ IEditorPart part,
+ IEditorInput input,
+ IFile file,
+ String id) {
+ IDE.setDefaultEditor(file, CommonXmlEditor.ID);
+
+ boolean ok = page.closeEditor(part, true /*save*/);
+
+ AdtPlugin.log(IStatus.INFO,
+ "Closed legacy editor ID %s for %s: %s", //$NON-NLS-1$
+ id,
+ file.getFullPath(),
+ ok ? "Success" : "Failed");//$NON-NLS-1$ //$NON-NLS-2$
+
+ if (ok) {
+ // Try to reopen it with the new ID
+ try {
+ page.openEditor(input, CommonXmlEditor.ID);
+ } catch (PartInitException e) {
+ AdtPlugin.log(e,
+ "Failed to reopen %s", //$NON-NLS-1$
+ file.getFullPath());
+ }
+ }
+ }
+ });
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/WidgetClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/WidgetClassLoader.java
new file mode 100644
index 000000000..682d6e538
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/WidgetClassLoader.java
@@ -0,0 +1,343 @@
+/*
+ * 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 org.eclipse.core.runtime.IProgressMonitor;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.management.InvalidAttributeValueException;
+
+/**
+ * Parser for the text file containing the list of widgets, layouts and layout params.
+ * <p/>
+ * The file is a straight text file containing one class per line.<br>
+ * Each line is in the following format<br>
+ * <code>[code][class name] [super class name] [super class name]...</code>
+ * where code is a single letter (W for widget, L for layout, P for layout params), and class names
+ * are the fully qualified name of the classes.
+ */
+public final class WidgetClassLoader implements IAndroidClassLoader {
+
+ /**
+ * Basic class containing the class descriptions found in the text file.
+ */
+ private final static class ClassDescriptor implements IClassDescriptor {
+
+ private String mFqcn;
+ private String mSimpleName;
+ private ClassDescriptor mSuperClass;
+ private ClassDescriptor mEnclosingClass;
+ private final ArrayList<IClassDescriptor> mDeclaredClasses =
+ new ArrayList<IClassDescriptor>();
+ private boolean mIsInstantiable = false;
+
+ ClassDescriptor(String fqcn) {
+ mFqcn = fqcn;
+ mSimpleName = getSimpleName(fqcn);
+ }
+
+ @Override
+ public String getFullClassName() {
+ return mFqcn;
+ }
+
+ @Override
+ public String getSimpleName() {
+ return mSimpleName;
+ }
+
+ @Override
+ public IClassDescriptor[] getDeclaredClasses() {
+ return mDeclaredClasses.toArray(new IClassDescriptor[mDeclaredClasses.size()]);
+ }
+
+ private void addDeclaredClass(ClassDescriptor declaredClass) {
+ mDeclaredClasses.add(declaredClass);
+ }
+
+ @Override
+ public IClassDescriptor getEnclosingClass() {
+ return mEnclosingClass;
+ }
+
+ void setEnclosingClass(ClassDescriptor enclosingClass) {
+ // set the enclosing class.
+ mEnclosingClass = enclosingClass;
+
+ // add this to the list of declared class in the enclosing class.
+ mEnclosingClass.addDeclaredClass(this);
+
+ // finally change the name of declared class to make sure it uses the
+ // convention: package.enclosing$declared instead of package.enclosing.declared
+ mFqcn = enclosingClass.mFqcn + "$" + mFqcn.substring(enclosingClass.mFqcn.length() + 1);
+ }
+
+ @Override
+ public IClassDescriptor getSuperclass() {
+ return mSuperClass;
+ }
+
+ void setSuperClass(ClassDescriptor superClass) {
+ mSuperClass = superClass;
+ }
+
+ @Override
+ public boolean equals(Object clazz) {
+ if (clazz instanceof ClassDescriptor) {
+ return mFqcn.equals(((ClassDescriptor)clazz).mFqcn);
+ }
+ return super.equals(clazz);
+ }
+
+ @Override
+ public int hashCode() {
+ return mFqcn.hashCode();
+ }
+
+ @Override
+ public boolean isInstantiable() {
+ return mIsInstantiable;
+ }
+
+ void setInstantiable(boolean state) {
+ mIsInstantiable = state;
+ }
+
+ private String getSimpleName(String fqcn) {
+ String[] segments = fqcn.split("\\.");
+ return segments[segments.length-1];
+ }
+ }
+
+ private BufferedReader mReader;
+
+ /** Output map of FQCN => descriptor on all classes */
+ private final Map<String, ClassDescriptor> mMap = new TreeMap<String, ClassDescriptor>();
+ /** Output map of FQCN => descriptor on View classes */
+ private final Map<String, ClassDescriptor> mWidgetMap = new TreeMap<String, ClassDescriptor>();
+ /** Output map of FQCN => descriptor on ViewGroup classes */
+ private final Map<String, ClassDescriptor> mLayoutMap = new TreeMap<String, ClassDescriptor>();
+ /** Output map of FQCN => descriptor on LayoutParams classes */
+ private final Map<String, ClassDescriptor> mLayoutParamsMap =
+ new HashMap<String, ClassDescriptor>();
+ /** File path of the source text file */
+ private String mOsFilePath;
+
+ /**
+ * Creates a loader with a given file path.
+ * @param osFilePath the OS path of the file to load.
+ * @throws FileNotFoundException if the file is not found.
+ */
+ WidgetClassLoader(String osFilePath) throws FileNotFoundException {
+ mOsFilePath = osFilePath;
+ mReader = new BufferedReader(new FileReader(osFilePath));
+ }
+
+ @Override
+ public String getSource() {
+ return mOsFilePath;
+ }
+
+ /**
+ * Parses the text file and return true if the file was successfully parsed.
+ * @param monitor
+ */
+ boolean parseWidgetList(IProgressMonitor monitor) {
+ try {
+ String line;
+ while ((line = mReader.readLine()) != null) {
+ if (line.length() > 0) {
+ char prefix = line.charAt(0);
+ String[] classes = null;
+ ClassDescriptor clazz = null;
+ switch (prefix) {
+ case 'W':
+ classes = line.substring(1).split(" ");
+ clazz = processClass(classes, 0, null /* map */);
+ if (clazz != null) {
+ clazz.setInstantiable(true);
+ mWidgetMap.put(classes[0], clazz);
+ }
+ break;
+ case 'L':
+ classes = line.substring(1).split(" ");
+ clazz = processClass(classes, 0, null /* map */);
+ if (clazz != null) {
+ clazz.setInstantiable(true);
+ mLayoutMap.put(classes[0], clazz);
+ }
+ break;
+ case 'P':
+ classes = line.substring(1).split(" ");
+ clazz = processClass(classes, 0, mLayoutParamsMap);
+ if (clazz != null) {
+ clazz.setInstantiable(true);
+ }
+ break;
+ case '#':
+ // comment, do nothing
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+ }
+
+ // reconciliate the layout and their layout params
+ postProcess();
+
+ return true;
+ } catch (IOException e) {
+ } finally {
+ try {
+ mReader.close();
+ } catch (IOException e) {
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Parses a View class and adds a ViewClassInfo for it in mWidgetMap.
+ * It calls itself recursively to handle super classes which are also Views.
+ * @param classes the inheritance list of the class to process.
+ * @param index the index of the class to process in the <code>classes</code> array.
+ * @param map an optional map in which to put every {@link ClassDescriptor} created.
+ */
+ private ClassDescriptor processClass(String[] classes, int index,
+ Map<String, ClassDescriptor> map) {
+ if (index >= classes.length) {
+ return null;
+ }
+
+ String fqcn = classes[index];
+
+ if ("java.lang.Object".equals(fqcn)) { //$NON-NLS-1$
+ return null;
+ }
+
+ // check if the ViewInfoClass has not yet been created.
+ if (mMap.containsKey(fqcn)) {
+ return mMap.get(fqcn);
+ }
+
+ // create the custom class.
+ ClassDescriptor clazz = new ClassDescriptor(fqcn);
+ mMap.put(fqcn, clazz);
+ if (map != null) {
+ map.put(fqcn, clazz);
+ }
+
+ // get the super class
+ ClassDescriptor superClass = processClass(classes, index+1, map);
+ if (superClass != null) {
+ clazz.setSuperClass(superClass);
+ }
+
+ return clazz;
+ }
+
+ /**
+ * Goes through the layout params and look for the enclosed class. If the layout params
+ * has no known enclosed type it is dropped.
+ */
+ private void postProcess() {
+ Collection<ClassDescriptor> params = mLayoutParamsMap.values();
+
+ for (ClassDescriptor param : params) {
+ String fqcn = param.getFullClassName();
+
+ // get the enclosed name.
+ String enclosed = getEnclosedName(fqcn);
+
+ // look for a match in the layouts. We don't use the layout map as it only contains the
+ // end classes, but in this case we also need to process the layout params for the base
+ // layout classes.
+ ClassDescriptor enclosingType = mMap.get(enclosed);
+ if (enclosingType != null) {
+ param.setEnclosingClass(enclosingType);
+
+ // remove the class from the map, and put it back with the fixed name
+ mMap.remove(fqcn);
+ mMap.put(param.getFullClassName(), param);
+ }
+ }
+ }
+
+ private String getEnclosedName(String fqcn) {
+ int index = fqcn.lastIndexOf('.');
+ return fqcn.substring(0, index);
+ }
+
+ /**
+ * Finds and loads all classes that derive from a given set of super classes.
+ *
+ * @param rootPackage Root package of classes to find. Use an empty string to find everyting.
+ * @param superClasses The super classes of all the classes to find.
+ * @return An hash map which keys are the super classes looked for and which values are
+ * ArrayList of the classes found. The array lists are always created for all the
+ * valid keys, they are simply empty if no deriving class is found for a given
+ * super class.
+ * @throws IOException
+ * @throws InvalidAttributeValueException
+ * @throws ClassFormatError
+ */
+ @Override
+ public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(String rootPackage,
+ String[] superClasses) throws IOException, InvalidAttributeValueException,
+ ClassFormatError {
+ HashMap<String, ArrayList<IClassDescriptor>> map =
+ new HashMap<String, ArrayList<IClassDescriptor>>();
+
+ ArrayList<IClassDescriptor> list = new ArrayList<IClassDescriptor>();
+ list.addAll(mWidgetMap.values());
+ map.put(SdkConstants.CLASS_VIEW, list);
+
+ list = new ArrayList<IClassDescriptor>();
+ list.addAll(mLayoutMap.values());
+ map.put(SdkConstants.CLASS_VIEWGROUP, list);
+
+ list = new ArrayList<IClassDescriptor>();
+ list.addAll(mLayoutParamsMap.values());
+ map.put(SdkConstants.CLASS_VIEWGROUP_LAYOUTPARAMS, list);
+
+ return map;
+ }
+
+ /**
+ * Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name.
+ * @param className the fully-qualified name of the class to return.
+ * @throws ClassNotFoundException
+ */
+ @Override
+ public IClassDescriptor getClass(String className) throws ClassNotFoundException {
+ return mMap.get(className);
+ }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/layout-devices.xsd b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/layout-devices.xsd
new file mode 100755
index 000000000..a4ea6c42e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/layout-devices.xsd
@@ -0,0 +1,345 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2009 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.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/layout-devices/1"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:c="http://schemas.android.com/sdk/android/layout-devices/1"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1">
+
+ <!-- The root element layout-devices defines a sequence of 0..n device elements. -->
+
+ <xsd:element name="layout-devices" type="c:layoutDevicesType" />
+
+ <xsd:complexType name="layoutDevicesType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The "layout-devices" element is the root element of this schema.
+
+ It must contain zero or more "device" elements that each define the configurations
+ available for a given device.
+
+ These definitions are used in the Graphical Layout Editor in the
+ Android Development Tools (ADT) plugin for Eclipse.
+ </xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:sequence>
+ <!-- layout-devices defines a sequence of 0..n device elements. -->
+ <xsd:element name="device" minOccurs="0" maxOccurs="unbounded">
+
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ A device element must contain at most one "default" element
+ followed by one or more "config" elements.
+
+ The "default" element defines all the default parameters
+ inherited by the following "config" elements.
+ Each "config" element can override the default values, if any.
+
+ A "device" element also has a required "name" attribute that
+ represents the user-interface name of this device.
+ </xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:complexType>
+ <!-- device defines a choice of 0..1 default element
+ and 1..n config elements. -->
+
+ <xsd:sequence>
+ <xsd:element name="default" type="c:parametersType"
+ minOccurs="0" maxOccurs="1" />
+ <xsd:element name="config" type="c:configType"
+ minOccurs="1" maxOccurs="unbounded" />
+ </xsd:sequence>
+
+ <xsd:attribute name="name" type="xsd:normalizedString" use="required" />
+ </xsd:complexType>
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <!-- The type of a device>default element.
+ This is overridden by configType below for the device>config element.
+ -->
+ <xsd:complexType name="parametersType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The parametersType define all the parameters that can happen either in a
+ "default" element or in a named "config" element.
+ Each parameter element can appear once at most.
+
+ Parameters here are the same as those used to specify alternate Android
+ resources, as documented by
+ http://d.android.com/guide/topics/resources/resources-i18n.html#AlternateResources
+ </xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:all>
+ <!-- parametersType says that 0..1 of each of these elements must be declared. -->
+
+ <xsd:element name="country-code" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the configuration is for a particular Mobile Country Code.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:float">
+ <xsd:minInclusive value="100" />
+ <xsd:maxInclusive value="999" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="network-code" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the configuration is for a particular Mobile Network Code.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:float">
+ <xsd:minExclusive value="0" />
+ <xsd:maxExclusive value="1000" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="screen-size" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies that the configuration is for a particular class of screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="small" />
+ <xsd:enumeration value="normal" />
+ <xsd:enumeration value="large" />
+ <xsd:enumeration value="xlarge" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="screen-ratio" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies that the configuration is for a taller/wider than traditional
+ screen. This is based purely on the aspect ration of the screen: QVGA,
+ HVGA, and VGA are notlong; WQVGA, WVGA, FWVGA are long. Note that long
+ may mean either wide or tall, depending on the current orientation.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="long" />
+ <xsd:enumeration value="notlong" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="screen-orientation" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies that the configuration is for a screen that is tall (port) or
+ wide (land).
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="port" />
+ <xsd:enumeration value="land" />
+ <xsd:enumeration value="square" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="pixel-density" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the screen density the configuration is defined for. The medium
+ density of traditional HVGA screens (mdpi) is defined to be approximately
+ 160dpi; low density (ldpi) is 120, and high density (hdpi) is 240. There
+ is thus a 4:3 scaling factor between each density, so a 9x9 bitmap in ldpi
+ would be 12x12 is mdpi and 16x16 in hdpi.
+ The special nodpi density that can be used in resource qualifiers is not
+ a valid keyword here.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="ldpi" />
+ <xsd:enumeration value="mdpi" />
+ <xsd:enumeration value="tvdpi" />
+ <xsd:enumeration value="hdpi" />
+ <xsd:enumeration value="xhdpi" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="touch-type" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the touch type the configuration is defined for.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="notouch" />
+ <xsd:enumeration value="stylus" />
+ <xsd:enumeration value="finger" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="keyboard-state" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ If your configuration uses a soft keyboard, use the keyssoft value.
+ If it doesn't and has a real keyboard, use keysexposed or keyshidden.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="keysexposed" />
+ <xsd:enumeration value="keyshidden" />
+ <xsd:enumeration value="keyssoft" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="text-input-method" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the primary text input method the configuration is designed for.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="nokeys" />
+ <xsd:enumeration value="qwerty" />
+ <xsd:enumeration value="12key" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="nav-state" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the primary non-touchscreen navigation control is
+ exposed or hidden.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="navexposed" />
+ <xsd:enumeration value="navhidden" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="nav-method" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the primary non-touchscreen navigation method the configuration
+ is designed for.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="dpad" />
+ <xsd:enumeration value="trackball" />
+ <xsd:enumeration value="wheel" />
+ <xsd:enumeration value="nonav" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="screen-dimension" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the device screen resolution, in pixels.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence minOccurs="2" maxOccurs="2">
+
+ <xsd:element name="size">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:positiveInteger" />
+ </xsd:simpleType>
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="xdpi" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the actual density in X of the device screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:float">
+ <xsd:minExclusive value="0" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="ydpi" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the actual density in Y of the device screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:float">
+ <xsd:minExclusive value="0" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ </xsd:all>
+ </xsd:complexType>
+
+ <!-- The type definition of a device>config element.
+ This type is basically all the element defined by parametersType and an extra
+ required "name" attribute for the user-interface configuration name.
+ -->
+ <xsd:complexType name="configType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The configType defines the content of a "config" element in a "device" element.
+
+ A "config" element can have all the parameters elements defined by
+ "parameterType". It also has a required "name" attribute that indicates the
+ user-interface name for this configuration.
+ </xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:complexContent>
+ <xsd:extension base="c:parametersType">
+ <xsd:attribute name="name" type="xsd:normalizedString" use="required" />
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+</xsd:schema>