Code drop from //branches/cupcake/...@124589

This commit is contained in:
The Android Open Source Project
2008-12-17 18:04:04 -08:00
parent 5c11852110
commit e943f2fd8e
659 changed files with 47382 additions and 9976 deletions

View File

@@ -5,5 +5,12 @@
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="lib" path="jarutils.jar"/>
<classpathentry kind="lib" path="androidprefs.jar"/>
<classpathentry kind="lib" path="sdkstats.jar"/>
<classpathentry kind="lib" path="kxml2-2.3.0.jar"/>
<classpathentry kind="lib" path="layoutlib_api.jar"/>
<classpathentry kind="lib" path="layoutlib_utils.jar"/>
<classpathentry kind="lib" path="ninepatch.jar"/>
<classpathentry kind="lib" path="sdklib.jar"/>
<classpathentry kind="lib" path="sdkuilib.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -2,14 +2,20 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Android Development Toolkit
Bundle-SymbolicName: com.android.ide.eclipse.adt;singleton:=true
Bundle-Version: 0.8.1.qualifier
Bundle-Version: 0.9.0.qualifier
Bundle-ClassPath: .,
jarutils.jar,
androidprefs.jar
androidprefs.jar,
sdkstats.jar,
kxml2-2.3.0.jar,
layoutlib_api.jar,
ninepatch.jar,
layoutlib_utils.jar,
sdklib.jar,
sdkuilib.jar
Bundle-Activator: com.android.ide.eclipse.adt.AdtPlugin
Bundle-Vendor: The Android Open Source Project
Require-Bundle: com.android.ide.eclipse.common,
com.android.ide.eclipse.ddms,
Require-Bundle: com.android.ide.eclipse.ddms,
org.eclipse.core.runtime,
org.eclipse.core.resources,
org.eclipse.debug.core,
@@ -26,11 +32,48 @@ Require-Bundle: com.android.ide.eclipse.common,
org.eclipse.core.filesystem,
org.eclipse.ui,
org.eclipse.ui.ide,
org.eclipse.ui.forms
org.eclipse.ui.forms,
org.eclipse.gef,
org.eclipse.ui.browser,
org.eclipse.ui.views,
org.eclipse.wst.sse.core,
org.eclipse.wst.sse.ui,
org.eclipse.wst.xml.core,
org.eclipse.wst.xml.ui
Eclipse-LazyStart: true
Export-Package: com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",
Export-Package: com.android.ide.eclipse.adt,
com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.adt.project;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.adt.project.internal;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.adt.resources;x-friends:="com.android.ide.eclipse.tests"
com.android.ide.eclipse.adt.sdk;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.adt.wizards.newproject;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.common,
com.android.ide.eclipse.common.project,
com.android.ide.eclipse.common.resources,
com.android.ide.eclipse.editors;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.descriptors;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.layout;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.layout.descriptors;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.layout.parts;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.layout.uimodel;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.manifest;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.manifest.descriptors;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.manifest.model;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.manifest.pages;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.menu;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.menu.descriptors;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources.configurations;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources.descriptors;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources.explorer;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources.manager;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources.manager.files;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources.uimodel;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.ui;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.ui.tree;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.uimodel;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.wizards;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.xml;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.xml.descriptors;x-friends:="com.android.ide.eclipse.tests"

View File

@@ -5,6 +5,13 @@ bin.includes = plugin.xml,\
templates/,\
about.ini,\
jarutils.jar,\
androidprefs.jar
androidprefs.jar,\
sdkstats.jar,\
kxml2-2.3.0.jar,\
layoutlib_api.jar,\
layoutlib_utils.jar,\
ninepatch.jar,\
sdklib.jar,\
sdkuilib.jar
source.. = src/
output.. = bin/

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

View File

@@ -1,6 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension
id="com.android.ide.eclipse.common.xmlProblem"
name="Android XML Format Problem"
point="org.eclipse.core.resources.markers">
<super type="org.eclipse.core.resources.problemmarker"/>
<super type="org.eclipse.core.resources.textmarker"/>
<persistent value="true"/>
</extension>
<extension
id="com.android.ide.eclipse.common.aaptProblem"
name="Android AAPT Problem"
point="org.eclipse.core.resources.markers">
<super type="org.eclipse.core.resources.problemmarker"/>
<super type="org.eclipse.core.resources.textmarker"/>
<persistent value="true"/>
</extension>
<extension
id="com.android.ide.eclipse.common.aidlProblem"
name="Android AIDL Problem"
point="org.eclipse.core.resources.markers">
<super type="org.eclipse.core.resources.problemmarker"/>
<super type="org.eclipse.core.resources.textmarker"/>
<persistent value="true"/>
</extension>
<extension
id="com.android.ide.eclipse.common.androidProblem"
name="Android XML Content Problem"
point="org.eclipse.core.resources.markers">
<super type="org.eclipse.core.resources.problemmarker"/>
<super type="org.eclipse.core.resources.textmarker"/>
<persistent value="true"/>
</extension>
<extension
id="ResourceManagerBuilder"
name="Android Resource Manager"
@@ -47,7 +79,7 @@
<wizard
canFinishEarly="false"
category="com.android.ide.eclipse.wizards.category"
class="com.android.ide.eclipse.adt.project.internal.NewProjectWizard"
class="com.android.ide.eclipse.adt.wizards.newproject.NewProjectWizard"
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
hasPages="true"
icon="icons/android.png"
@@ -55,6 +87,18 @@
name="Android Project"
preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
project="true"/>
<wizard
canFinishEarly="false"
category="com.android.ide.eclipse.wizards.category"
class="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard"
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
hasPages="true"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard"
name="Android XML File"
preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
project="false">
</wizard>
</extension>
<extension
point="org.eclipse.debug.core.launchConfigurationTypes">
@@ -173,6 +217,13 @@
id="com.android.ide.eclipse.adt.project.CreateAidlImportAction"
label="Create Aidl preprocess file for Parcelable classes"
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1"/>
<action
class="com.android.ide.eclipse.adt.project.NewXmlFileWizardAction"
enablesFor="1"
id="com.android.ide.eclipse.adt.project.NewXmlFileWizardAction"
label="New Resource File..."
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1">
</action>
<action
class="com.android.ide.eclipse.adt.project.ExportAction"
enablesFor="1"
@@ -183,7 +234,7 @@
class="com.android.ide.eclipse.adt.project.ExportWizardAction"
enablesFor="1"
id="com.android.ide.eclipse.adt.project.ExportWizardAction"
label="Export Application..."
label="Export Signed Application Package..."
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group2"/>
<action
class="com.android.ide.eclipse.adt.project.FixProjectAction"
@@ -209,29 +260,31 @@
class="com.android.ide.eclipse.adt.preferences.LaunchPreferencePage"
id="com.android.ide.eclipse.adt.preferences.LaunchPreferencePage"
name="Launch"/>
<page
category="com.android.ide.eclipse.preferences.main"
class="com.android.ide.eclipse.common.preferences.UsagePreferencePage"
id="com.android.ide.eclipse.common.preferences.UsagePreferencePage"
name="Usage Stats">
</page>
</extension>
<extension
point="org.eclipse.core.runtime.preferences">
<initializer class="com.android.ide.eclipse.adt.preferences.PreferenceInitializer"/>
</extension>
<extension
point="org.eclipse.ui.editors">
<editor
class="com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor"
contributorClass="org.eclipse.ui.texteditor.BasicTextEditorActionContributor"
default="true"
filenames="R.java, Manifest.java"
icon="icons/android.png"
id="com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor"
name="Android Java Editor"/>
</extension>
<extension
id="com.android.ide.eclipse.adt.adtProblem"
name="Generic ADT Problem"
name="Android ADT Problem"
point="org.eclipse.core.resources.markers">
<super type="org.eclipse.core.resources.problemmarker"/>
<persistent value="true"/>
</extension>
<extension
id="com.android.ide.eclipse.adt.targetProblem"
name="Android Target Problem"
point="org.eclipse.core.resources.markers">
<super type="org.eclipse.core.resources.problemmarker"/>
<persistent value="false"/>
</extension>
<extension
point="org.eclipse.ui.perspectiveExtensions">
<perspectiveExtension targetID="org.eclipse.jdt.ui.JavaPerspective">
@@ -305,4 +358,110 @@
keyConfigurationId="org.eclipse.ui.defaultAcceleratorConfiguration">
</keyBinding>
</extension>
<extension
point="org.eclipse.ui.decorators">
<decorator
adaptable="true"
class="com.android.ide.eclipse.adt.project.FolderDecorator"
id="com.android.ide.eclipse.adt.project.FolderDecorator"
label="Android Decorator"
lightweight="true"
location="TOP_RIGHT"
objectClass="org.eclipse.core.resources.IFolder"
state="true">
</decorator>
</extension>
<extension
point="org.eclipse.ui.editors">
<editor
class="com.android.ide.eclipse.editors.manifest.ManifestEditor"
default="true"
filenames="AndroidManifest.xml"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.manifest.ManifestEditor"
name="Android Manifest Editor">
</editor>
<editor
class="com.android.ide.eclipse.editors.resources.ResourcesEditor"
default="false"
extensions="xml"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.resources.ResourcesEditor"
name="Android Resource Editor">
</editor>
<editor
class="com.android.ide.eclipse.editors.layout.LayoutEditor"
default="false"
extensions="xml"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.layout.LayoutEditor"
matchingStrategy="com.android.ide.eclipse.editors.layout.MatchingStrategy"
name="Android Layout Editor">
</editor>
<editor
class="com.android.ide.eclipse.editors.menu.MenuEditor"
default="false"
extensions="xml"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.menu.MenuEditor"
name="Android Menu Editor">
</editor>
<editor
class="com.android.ide.eclipse.editors.xml.XmlEditor"
default="false"
extensions="xml"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.xml.XmlEditor"
name="Android Xml Resources Editor">
</editor>
</extension>
<extension
point="org.eclipse.ui.views">
<view
allowMultiple="false"
category="com.android.ide.eclipse.ddms.views.category"
class="com.android.ide.eclipse.editors.resources.explorer.ResourceExplorerView"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.resources.explorer.ResourceExplorerView"
name="Resource Explorer">
</view>
</extension>
<extension
point="org.eclipse.wst.sse.ui.editorConfiguration">
<sourceViewerConfiguration
class="com.android.ide.eclipse.editors.manifest.ManifestSourceViewerConfig"
target="com.android.ide.eclipse.editors.manifest.ManifestEditor">
</sourceViewerConfiguration>
<sourceViewerConfiguration
class="com.android.ide.eclipse.editors.resources.ResourcesSourceViewerConfig"
target="com.android.ide.eclipse.editors.resources.ResourcesEditor">
</sourceViewerConfiguration>
<sourceViewerConfiguration
class="com.android.ide.eclipse.editors.layout.LayoutSourceViewerConfig"
target="com.android.ide.eclipse.editors.layout.LayoutEditor">
</sourceViewerConfiguration>
<sourceViewerConfiguration
class="com.android.ide.eclipse.editors.menu.MenuSourceViewerConfig"
target="com.android.ide.eclipse.editors.menu.MenuEditor">
</sourceViewerConfiguration>
<sourceViewerConfiguration
class="com.android.ide.eclipse.editors.xml.XmlSourceViewerConfig"
target="com.android.ide.eclipse.editors.xml.XmlEditor">
</sourceViewerConfiguration>
</extension>
<extension
point="org.eclipse.ui.propertyPages">
<page
adaptable="true"
class="com.android.ide.eclipse.adt.project.properties.AndroidPropertyPage"
id="com.android.ide.eclipse.adt.project.properties.AndroidPropertyPage"
name="Android"
nameFilter="*"
objectClass="org.eclipse.core.resources.IProject">
<enabledWhen>
<test property="org.eclipse.jdt.launching.hasProjectNature"
args="com.android.ide.eclipse.adt.AndroidNature"/>
</enabledWhen>
</page>
</extension>
</plugin>

View File

@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt;
/**
* Constant definition class.<br>
* <br>
@@ -39,6 +40,12 @@ public class AdtConstants {
/** Generic marker for ADT errors. */
public final static String MARKER_ADT = AdtPlugin.PLUGIN_ID + ".adtProblem"; //$NON-NLS-1$
/** Marker for Android Target errors.
* This is not cleared on each like other markers. Instead, it's cleared
* when a ContainerClasspathInitialized has succeeded in creating an
* {@link AndroidClasspathContainer}*/
public final static String MARKER_TARGET = AdtPlugin.PLUGIN_ID + ".targetProblem"; //$NON-NLS-1$
/** Build verbosity "Always". Those messages are always displayed. */
public final static int BUILD_ALWAYS = 0;

View File

@@ -22,34 +22,55 @@ import com.android.ddmuilib.console.DdmConsole;
import com.android.ddmuilib.console.IDdmConsole;
import com.android.ide.eclipse.adt.build.DexWrapper;
import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController;
import com.android.ide.eclipse.adt.debug.ui.SkinRepository;
import com.android.ide.eclipse.adt.preferences.BuildPreferencePage;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard;
import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer;
import com.android.ide.eclipse.adt.resources.FrameworkResourceParser;
import com.android.ide.eclipse.adt.sdk.AndroidTargetParser;
import com.android.ide.eclipse.adt.sdk.LoadStatus;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.CommonPlugin;
import com.android.ide.eclipse.common.EclipseUiHelper;
import com.android.ide.eclipse.common.SdkStatsHelper;
import com.android.ide.eclipse.common.StreamHelper;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.ide.eclipse.common.project.ExportHelper;
import com.android.ide.eclipse.common.project.ExportHelper.IExportCallback;
import com.android.ide.eclipse.common.resources.FrameworkResourceManager;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.ide.eclipse.ddms.ImageLoader;
import com.android.ide.eclipse.editors.IconFactory;
import com.android.ide.eclipse.editors.layout.LayoutEditor;
import com.android.ide.eclipse.editors.menu.MenuEditor;
import com.android.ide.eclipse.editors.resources.ResourcesEditor;
import com.android.ide.eclipse.editors.resources.manager.ProjectResources;
import com.android.ide.eclipse.editors.resources.manager.ResourceFolder;
import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType;
import com.android.ide.eclipse.editors.resources.manager.ResourceManager;
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor;
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener;
import com.android.ide.eclipse.editors.xml.XmlEditor;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Preferences;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.Preferences.IPropertyChangeListener;
import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
@@ -57,14 +78,21 @@ import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleConstants;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@@ -81,6 +109,8 @@ import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* The activator class controls the plug-in life cycle
@@ -106,14 +136,17 @@ public class AdtPlugin extends AbstractUIPlugin {
/** singleton instance */
private static AdtPlugin sPlugin;
private static Image sAndroidLogo;
private static ImageDescriptor sAndroidLogoDesc;
/** default store, provided by eclipse */
private IPreferenceStore mStore;
/** cached location for the sdk folder */
private String mOsSdkLocation;
/** SDK Api Version */
String mSdkApiVersion;
/** The global android console */
private MessageConsole mAndroidConsole;
/** Stream to write in the android console */
private MessageConsoleStream mAndroidConsoleStream;
@@ -130,14 +163,14 @@ public class AdtPlugin extends AbstractUIPlugin {
/** Color used in the error console */
private Color mRed;
private final ArrayList<IJavaProject> mPostDexProjects = new ArrayList<IJavaProject>();
/** Load status of the SDK. Any access MUST be in a synchronized(mPostLoadProjects) block */
private LoadStatus mSdkIsLoaded = LoadStatus.LOADING;
/** Project to update once the SDK is loaded.
* Any access MUST be in a synchronized(mPostLoadProjects) block */
private final ArrayList<IJavaProject> mPostLoadProjects = new ArrayList<IJavaProject>();
/** Boolean wrapper to run dialog in the UI thread, and still get the
* return code.
*/
private static final class BooleanWrapper {
public boolean b;
}
private ResourceMonitor mResourceMonitor;
private ArrayList<Runnable> mResourceRefreshListener = new ArrayList<Runnable>();
/**
* Custom PrintStream for Dx output. This class overrides the method
@@ -210,10 +243,14 @@ public class AdtPlugin extends AbstractUIPlugin {
Display display = getDisplay();
// set the default android console.
mAndroidConsole = new MessageConsole("Android", null); //$NON-NLS-1$
ConsolePlugin.getDefault().getConsoleManager().addConsoles(
new IConsole[] { mAndroidConsole });
// get the stream to write in the android console.
MessageConsole androidConsole = CommonPlugin.getDefault().getAndroidConsole();
mAndroidConsoleStream = androidConsole.newMessageStream();
mAndroidConsoleErrorStream = androidConsole.newMessageStream();
mAndroidConsoleStream = mAndroidConsole.newMessageStream();
mAndroidConsoleErrorStream = mAndroidConsole.newMessageStream();
mRed = new Color(display, 0xFF, 0x00, 0x00);
// because this can be run, in some cases, by a non ui thread, and beccause
@@ -267,22 +304,19 @@ public class AdtPlugin extends AbstractUIPlugin {
// get the SDK location and build id.
if (checkSdkLocationAndId()) {
// if sdk if valid, reparse the skin folder
SkinRepository.getInstance().parseFolder(getOsSkinFolder());
// if sdk if valid, reparse it
// add the current Android project to the list of projects to be updated
// after the SDK is reloaded
synchronized (mPostLoadProjects) {
// get the project to refresh.
IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects();
mPostLoadProjects.addAll(Arrays.asList(androidProjects));
}
// parse the SDK resources at the new location
parseSdkContent();
}
// parse the SDK resources at the new location
parseSdkContent();
// get the project to refresh.
IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects();
// Setup the new container for each project. By providing new instances of
// AndroidClasspathContainer, this will force JDT to call
// IClasspathContainer#getClasspathEntries() again and receive the new
// path to the framework jar.
AndroidClasspathContainerInitializer.updateProjects(androidProjects);
} else if (PREFS_BUILD_VERBOSITY.equals(property)) {
mBuildVerbosity = BuildPreferencePage.getBuildLevel(
mStore.getString(PREFS_BUILD_VERBOSITY));
@@ -299,20 +333,11 @@ public class AdtPlugin extends AbstractUIPlugin {
}
// check the location of SDK
if (checkSdkLocationAndId()) {
// if sdk if valid, parse the skin folder
SkinRepository.getInstance().parseFolder(getOsSkinFolder());
// parse the SDK resources.
parseSdkContent();
}
final boolean isSdkLocationValid = checkSdkLocationAndId();
mBuildVerbosity = BuildPreferencePage.getBuildLevel(
mStore.getString(PREFS_BUILD_VERBOSITY));
// Ping the usage start server.
pingUsageServer();
// create the loader that's able to load the images
mLoader = new ImageLoader(this);
@@ -356,18 +381,31 @@ public class AdtPlugin extends AbstractUIPlugin {
dialog.open();
}
});
// initialize editors
startEditors();
/* The Editors plugin must be started as soon as Android projects are opened or created,
* in order to properly set default editors on the layout/values XML files.
*
* This ensures that the default editors is really only set when a new XML file
* is added to the workspace (IResourceDelta.ADDED event), through project creation or
* manual add.
* Other methods would force to go through existing projects when the Editors plugin is
* started, and set the default editors for their XML files, possibly erasing user set
* default editors.
*/
startEditorsPlugin();
// Ping the usage server and parse the SDK content.
// This is deferred in separate jobs to avoid blocking the bundle start.
// We also serialize them to avoid too many parallel jobs when Eclipse starts.
Job pingJob = createPingUsageServerJob();
pingJob.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
super.done(event);
// Once the ping job is finished, start the SDK parser
if (isSdkLocationValid) {
// parse the SDK resources.
parseSdkContent();
}
}
});
// build jobs are run after other interactive jobs
pingJob.setPriority(Job.BUILD);
// Wait 2 seconds before starting the ping job. This leaves some time to the
// other bundles to initialize.
pingJob.schedule(2000 /*milliseconds*/);
}
/*
@@ -379,6 +417,8 @@ public class AdtPlugin extends AbstractUIPlugin {
public void stop(BundleContext context) throws Exception {
super.stop(context);
stopEditors();
DexWrapper.unloadDex();
mRed.dispose();
@@ -418,37 +458,22 @@ public class AdtPlugin extends AbstractUIPlugin {
/** Returns the adb path relative to the sdk folder */
public static String getOsRelativeAdb() {
return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_ADB;
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_ADB;
}
/** Returns the aapt path relative to the sdk folder */
public static String getOsRelativeAapt() {
return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AAPT;
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AAPT;
}
/** Returns the emulator path relative to the sdk folder */
public static String getOsRelativeEmulator() {
return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_EMULATOR;
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_EMULATOR;
}
/** Returns the aidl path relative to the sdk folder */
public static String getOsRelativeAidl() {
return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AIDL;
}
/** Returns the framework jar path relative to the sdk folder */
public static String getOsRelativeFramework() {
return AndroidConstants.FN_FRAMEWORK_LIBRARY;
}
/** Returns the android sources path relative to the sdk folder */
public static String getOsRelativeAndroidSources() {
return AndroidConstants.FD_ANDROID_SOURCES;
}
/** Returns the framework jar path relative to the sdk folder */
public static String getOsRelativeAttrsXml() {
return AndroidConstants.OS_SDK_LIBS_FOLDER + AndroidConstants.FN_ATTRS_XML;
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AIDL;
}
/** Returns the absolute adb path */
@@ -458,7 +483,7 @@ public class AdtPlugin extends AbstractUIPlugin {
/** Returns the absolute traceview path */
public static String getOsAbsoluteTraceview() {
return getOsSdkFolder() + AndroidConstants.OS_SDK_TOOLS_FOLDER +
return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER +
AndroidConstants.FN_TRACEVIEW;
}
@@ -467,21 +492,6 @@ public class AdtPlugin extends AbstractUIPlugin {
return getOsSdkFolder() + getOsRelativeAapt();
}
/** Returns the absolute sdk framework path */
public static String getOsAbsoluteFramework() {
return getOsSdkFolder() + getOsRelativeFramework();
}
/** Returns the absolute android sources path in the sdk */
public static String getOsAbsoluteAndroidSources() {
return getOsSdkFolder() + getOsRelativeAndroidSources();
}
/** Returns the absolute attrs.xml path */
public static String getOsAbsoluteAttrsXml() {
return getOsSdkFolder() + getOsRelativeAttrsXml();
}
/** Returns the absolute emulator path */
public static String getOsAbsoluteEmulator() {
return getOsSdkFolder() + getOsRelativeEmulator();
@@ -492,16 +502,6 @@ public class AdtPlugin extends AbstractUIPlugin {
return getOsSdkFolder() + getOsRelativeAidl();
}
/** Returns the absolute path to the aidl framework import file. */
public static String getOsAbsoluteFrameworkAidl() {
return getOsSdkFolder() + AndroidConstants.OS_SDK_LIBS_FOLDER +
AndroidConstants.FN_FRAMEWORK_AIDL;
}
public static String getOsSdkSamplesFolder() {
return getOsSdkFolder() + AndroidConstants.OS_SDK_SAMPLES_FOLDER;
}
/**
* Returns a Url file path to the javaDoc folder.
*/
@@ -526,11 +526,7 @@ public class AdtPlugin extends AbstractUIPlugin {
}
public static String getOsSdkToolsFolder() {
return getOsSdkFolder() + AndroidConstants.OS_SDK_TOOLS_FOLDER;
}
public static String getOsSkinFolder() {
return getOsSdkFolder() + AndroidConstants.OS_SDK_SKINS_FOLDER;
return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER;
}
public static synchronized boolean getAutoResRefresh() {
@@ -540,18 +536,6 @@ public class AdtPlugin extends AbstractUIPlugin {
return sPlugin.mStore.getBoolean(PREFS_RES_AUTO_REFRESH);
}
/**
* Returns the SDK build id.
* @return a string containing the SDK build id, or null it it is unknownn.
*/
public static synchronized String getSdkApiVersion() {
if (sPlugin != null) {
return sPlugin.mSdkApiVersion;
}
return null;
}
public static synchronized int getBuildVerbosity() {
if (sPlugin != null) {
return sPlugin.mBuildVerbosity;
@@ -705,23 +689,25 @@ public class AdtPlugin extends AbstractUIPlugin {
final Display display = getDisplay();
// we need to ask the user what he wants to do.
final BooleanWrapper wrapper = new BooleanWrapper();
final boolean[] result = new boolean[1];
display.syncExec(new Runnable() {
public void run() {
Shell shell = display.getActiveShell();
wrapper.b = MessageDialog.openQuestion(shell, title, message);
result[0] = MessageDialog.openQuestion(shell, title, message);
}
});
return wrapper.b;
return result[0];
}
/**
* Logs a message to the default Eclipse log.
*
* @param severity The severity code. Valid values are: {@link IStatus#OK}, {@link IStatus#ERROR},
* {@link IStatus#INFO}, {@link IStatus#WARNING} or {@link IStatus#CANCEL}.
* @param severity The severity code. Valid values are: {@link IStatus#OK},
* {@link IStatus#ERROR}, {@link IStatus#INFO}, {@link IStatus#WARNING} or
* {@link IStatus#CANCEL}.
* @param format The format string, like for {@link String#format(String, Object...)}.
* @param args The arguments for the format string, like for {@link String#format(String, Object...)}.
* @param args The arguments for the format string, like for
* {@link String#format(String, Object...)}.
*/
public static void log(int severity, String format, Object ... args) {
String message = String.format(format, args);
@@ -845,7 +831,7 @@ public class AdtPlugin extends AbstractUIPlugin {
// now make sure it's not docked.
ConsolePlugin.getDefault().getConsoleManager().showConsoleView(
CommonPlugin.getDefault().getAndroidConsole());
AdtPlugin.getDefault().getAndroidConsole());
}
/**
@@ -883,21 +869,17 @@ public class AdtPlugin extends AbstractUIPlugin {
}
/**
* Adds a {@link IJavaProject} to a list of projects to be recompiled once dx.jar is loaded.
* @param javaProject
* Returns whether the Sdk has been loaded. If the SDK has not been loaded, the given
* <var>project</var> is added to a list of projects to recompile after the SDK is loaded.
*/
public void addPostDexProject(IJavaProject javaProject) {
synchronized (mPostDexProjects) {
if (DexWrapper.getStatus() == DexWrapper.LoadStatus.LOADED) {
// Setup the new container for each project. By providing new instances of
// AndroidClasspathContainer, this will force JDT to call
// IClasspathContainer#getClasspathEntries() again and receive the new
// path to the framework jar, and the project will be recompiled.
AndroidClasspathContainerInitializer.updateProjects(new IJavaProject [] {
javaProject });
} else {
mPostDexProjects.add(javaProject);
public LoadStatus getSdkLoadStatus(IJavaProject project) {
synchronized (mPostLoadProjects) {
// only add the project to the list, if we are still loading.
if (mSdkIsLoaded == LoadStatus.LOADING && project != null) {
mPostLoadProjects.add(project);
}
return mSdkIsLoaded;
}
}
@@ -907,9 +889,6 @@ public class AdtPlugin extends AbstractUIPlugin {
* @return false if the location is not correct.
*/
private boolean checkSdkLocationAndId() {
// Reset the sdk build first in case the SDK is invalid and we abort.
mSdkApiVersion = null;
if (mOsSdkLocation == null || mOsSdkLocation.length() == 0) {
displayError(Messages.Dialog_Title_SDK_Location, Messages.SDK_Not_Setup);
return false;
@@ -949,17 +928,16 @@ public class AdtPlugin extends AbstractUIPlugin {
String.format(Messages.Could_Not_Find_Folder, osSdkLocation));
}
String osTools = osSdkLocation + AndroidConstants.OS_SDK_TOOLS_FOLDER;
String osTools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER;
File toolsFolder = new File(osTools);
if (toolsFolder.isDirectory() == false) {
return errorHandler.handleError(
String.format(Messages.Could_Not_Find_Folder_In_SDK,
AndroidConstants.FD_TOOLS, osSdkLocation));
SdkConstants.FD_TOOLS, osSdkLocation));
}
// check the path to various tools we use
String[] filesToCheck = new String[] {
osSdkLocation + getOsRelativeFramework(),
osSdkLocation + getOsRelativeAdb(),
osSdkLocation + getOsRelativeAapt(),
osSdkLocation + getOsRelativeAidl(),
@@ -990,118 +968,106 @@ public class AdtPlugin extends AbstractUIPlugin {
}
/**
* Pings the usage start server.
* Creates a job than can ping the usage server.
*/
private void pingUsageServer() {
private Job createPingUsageServerJob() {
// In order to not block the plugin loading, so we spawn another thread.
new Thread("Ping!") { //$NON-NLS-1$
Job job = new Job("Android SDK Ping") { // Job name, visible in progress view
@Override
public void run() {
// get the version of the plugin
String versionString = (String) getBundle().getHeaders().get(
Constants.BUNDLE_VERSION);
Version version = new Version(versionString);
SdkStatsHelper.pingUsageServer("adt", version); //$NON-NLS-1$
}
}.start();
}
/**
* Starts the Editors plugin.
* <p/>
* Since we do not want any dependencies between the plugins (Editors is an optional
* plugin not needed for Android development), we attempt to start the plugin through
* OSGi directly.
* <p/>
* This is done in another thread to not delay the start of this plugin.
*/
private void startEditorsPlugin() {
new Thread() {
@Override
public void run() {
protected IStatus run(IProgressMonitor monitor) {
try {
// look for the bundle of the Editors plugin
Bundle editorsBundle = Platform.getBundle(AndroidConstants.EDITORS_PLUGIN_ID);
if (editorsBundle != null) {
// we only start if the bundle is installed and not started.
// STARTING means that its start is pending a triggering.
int bundleState = editorsBundle.getState();
if ((bundleState & (Bundle.RESOLVED | Bundle.INSTALLED
| Bundle.STARTING)) != 0) {
// Attempt to start it.
// START_TRANSIENT is used because we don't want
// to change the auto start value.
editorsBundle.start(Bundle.START_TRANSIENT);
}
}
} catch (Exception e) {
log(e, Messages.AdtPlugin_Failed_To_Start_s, AndroidConstants.EDITORS_PLUGIN_ID);
// get the version of the plugin
String versionString = (String) getBundle().getHeaders().get(
Constants.BUNDLE_VERSION);
Version version = new Version(versionString);
SdkStatsHelper.pingUsageServer("editors", version); //$NON-NLS-1$
return Status.OK_STATUS;
} catch (Throwable t) {
log(t, "pingUsageServer failed"); //$NON-NLS-1$
return new Status(IStatus.ERROR, PLUGIN_ID,
"pingUsageServer failed", t);
}
}
}.start();
};
return job;
}
/**
* Parses the SDK resources and set them in the {@link FrameworkResourceManager}.
* Parses the SDK resources.
*/
private void parseSdkContent() {
// Perform the update in a thread (here an Eclipse runtime job)
// since this should never block the caller (especially the start method)
new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) {
Job job = new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) {
@SuppressWarnings("unchecked")
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
SubMonitor progress = null;
try {
progress = SubMonitor.convert(monitor, Messages.AdtPlugin_Parsing_Resources, 100);
// load the values.
FrameworkResourceParser parser = new FrameworkResourceParser();
parser.parse(mOsSdkLocation, FrameworkResourceManager.getInstance(),
progress);
// set the location of the layout lib jar file.
FrameworkResourceManager.getInstance().setLayoutLibLocation(
mOsSdkLocation + AndroidConstants.OS_SDK_LIBS_LAYOUTLIB_JAR);
FrameworkResourceManager.getInstance().setFrameworkResourcesLocation(
mOsSdkLocation + AndroidConstants.OS_SDK_RESOURCES_FOLDER);
FrameworkResourceManager.getInstance().setFrameworkFontLocation(
mOsSdkLocation + AndroidConstants.OS_SDK_FONTS_FOLDER);
} catch (Throwable e) {
AdtPlugin.log(e, "Android SDK Resource Parser failed"); //$NON-NLS-1$
AdtPlugin.printErrorToConsole(Messages.AdtPlugin_Android_SDK_Resource_Parser,
Messages.AdtPlugin_Failed_To_Parse_s + e.getMessage());
return new Status(IStatus.ERROR, PLUGIN_ID, e.getMessage(), e);
} finally {
if (progress != null) {
progress.worked(100);
}
}
try {
SubMonitor progress = SubMonitor.convert(monitor,
"Initialize SDK Manager", 100);
try {
progress = SubMonitor.convert(monitor, Messages.AdtPlugin_Parsing_Resources, 20);
Sdk sdk = Sdk.loadSdk(mOsSdkLocation);
if (sdk != null) {
progress.setTaskName(Messages.AdtPlugin_Parsing_Resources);
for (IAndroidTarget target : sdk.getTargets()) {
IStatus status = new AndroidTargetParser(target).run(progress);
if (status.getCode() != IStatus.OK) {
synchronized (mPostLoadProjects) {
mSdkIsLoaded = LoadStatus.FAILED;
mPostLoadProjects.clear();
}
return status;
}
}
// FIXME: move this per platform, or somewhere else.
progress = SubMonitor.convert(monitor,
Messages.AdtPlugin_Parsing_Resources, 20);
DexWrapper.unloadDex();
IStatus res = DexWrapper.loadDex(
mOsSdkLocation + AndroidConstants.OS_SDK_LIBS_DX_JAR);
if (res != Status.OK_STATUS) {
synchronized (mPostLoadProjects) {
mSdkIsLoaded = LoadStatus.FAILED;
mPostLoadProjects.clear();
}
return res;
} else {
}
synchronized (mPostLoadProjects) {
mSdkIsLoaded = LoadStatus.LOADED;
// update the project that needs recompiling.
synchronized (mPostDexProjects) {
if (mPostDexProjects.size() > 0) {
IJavaProject[] array = mPostDexProjects.toArray(
new IJavaProject[mPostDexProjects.size()]);
AndroidClasspathContainerInitializer.updateProjects(array);
mPostDexProjects.clear();
}
if (mPostLoadProjects.size() > 0) {
IJavaProject[] array = mPostLoadProjects.toArray(
new IJavaProject[mPostLoadProjects.size()]);
AndroidClasspathContainerInitializer.updateProjects(array);
mPostLoadProjects.clear();
}
}
} finally {
if (progress != null) {
progress.worked(20);
}
// Notify resource changed listeners
progress.subTask("Refresh UI");
progress.setWorkRemaining(mResourceRefreshListener.size());
// Clone the list before iterating, to avoid Concurrent Modification
// exceptions
List<Runnable> listeners = (List<Runnable>)mResourceRefreshListener.clone();
for (Runnable listener : listeners) {
try {
AdtPlugin.getDisplay().syncExec(listener);
} catch (Exception e) {
AdtPlugin.log(e, "ResourceRefreshListener Failed"); //$NON-NLS-1$
} finally {
progress.worked(1);
}
}
} finally {
@@ -1112,6 +1078,252 @@ public class AdtPlugin extends AbstractUIPlugin {
return Status.OK_STATUS;
}
}.schedule();
};
job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs
job.schedule();
}
/** Returns the global android console */
public MessageConsole getAndroidConsole() {
return mAndroidConsole;
}
// ----- Methods for Editors -------
public void startEditors() {
sAndroidLogoDesc = imageDescriptorFromPlugin(AdtPlugin.PLUGIN_ID,
"/icons/android.png"); //$NON-NLS-1$
sAndroidLogo = sAndroidLogoDesc.createImage();
// get the stream to write in the android console.
MessageConsole androidConsole = AdtPlugin.getDefault().getAndroidConsole();
mAndroidConsoleStream = androidConsole.newMessageStream();
mAndroidConsoleErrorStream = androidConsole.newMessageStream();
mRed = new Color(getDisplay(), 0xFF, 0x00, 0x00);
// because this can be run, in some cases, by a non ui thread, and beccause
// changing the console properties update the ui, we need to make this change
// in the ui thread.
getDisplay().asyncExec(new Runnable() {
public void run() {
mAndroidConsoleErrorStream.setColor(mRed);
}
});
// Add a resource listener to handle compiled resources.
IWorkspace ws = ResourcesPlugin.getWorkspace();
mResourceMonitor = ResourceMonitor.startMonitoring(ws);
if (mResourceMonitor != null) {
try {
setupDefaultEditor(mResourceMonitor);
ResourceManager.setup(mResourceMonitor);
} catch (Throwable t) {
log(t, "ResourceManager.setup failed"); //$NON-NLS-1$
}
}
}
/**
* The <code>AbstractUIPlugin</code> implementation of this <code>Plugin</code>
* method saves this plug-in's preference and dialog stores and shuts down
* its image registry (if they are in use). Subclasses may extend this
* method, but must send super <b>last</b>. A try-finally statement should
* be used where necessary to ensure that <code>super.shutdown()</code> is
* always done.
*
* @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
*/
public void stopEditors() {
sAndroidLogo.dispose();
IconFactory.getInstance().Dispose();
// Remove the resource listener that handles compiled resources.
IWorkspace ws = ResourcesPlugin.getWorkspace();
ResourceMonitor.stopMonitoring(ws);
mRed.dispose();
}
/**
* Returns an Image for the small Android logo.
*
* Callers should not dispose it.
*/
public static Image getAndroidLogo() {
return sAndroidLogo;
}
/**
* Returns an {@link ImageDescriptor} for the small Android logo.
*
* Callers should not dispose it.
*/
public static ImageDescriptor getAndroidLogoDesc() {
return sAndroidLogoDesc;
}
/**
* Returns the ResourceMonitor object.
*/
public ResourceMonitor getResourceMonitor() {
return mResourceMonitor;
}
/**
* Sets up the editor to register default editors for resource files when needed.
*
* This is called by the {@link AdtPlugin} during initialization.
*
* @param monitor The main Resource Monitor object.
*/
public void setupDefaultEditor(ResourceMonitor monitor) {
monitor.addFileListener(new IFileListener() {
private static final String UNKNOWN_EDITOR = "unknown-editor"; //$NON-NLS-1$
/* (non-Javadoc)
* Sent when a file changed.
* @param file The file that changed.
* @param markerDeltas The marker deltas for the file.
* @param kind The change kind. This is equivalent to
* {@link IResourceDelta#accept(IResourceDeltaVisitor)}
*
* @see IFileListener#fileChanged
*/
public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
if (AndroidConstants.EXT_XML.equals(file.getFileExtension())) {
// The resources files must have a file path similar to
// project/res/.../*.xml
// There is no support for sub folders, so the segment count must be 4
if (file.getFullPath().segmentCount() == 4) {
// check if we are inside the res folder.
String segment = file.getFullPath().segment(1);
if (segment.equalsIgnoreCase(AndroidConstants.FD_RESOURCES)) {
// we are inside a res/ folder, get the actual ResourceFolder
ProjectResources resources = ResourceManager.getInstance().
getProjectResources(file.getProject());
// This happens when importing old Android projects in Eclipse
// that lack the container (probably because resources fail to build
// properly.)
if (resources == null) {
log(IStatus.INFO,
"getProjectResources failed for path %1$s in project %2$s", //$NON-NLS-1$
file.getFullPath().toOSString(),
file.getProject().getName());
return;
}
ResourceFolder resFolder = resources.getResourceFolder(
(IFolder)file.getParent());
if (resFolder != null) {
if (kind == IResourceDelta.ADDED) {
resourceAdded(file, resFolder.getType());
} else if (kind == IResourceDelta.CHANGED) {
resourceChanged(file, resFolder.getType());
}
} else {
// if the res folder is null, this means the name is invalid,
// in this case we remove whatever android editors that was set
// as the default editor.
IEditorDescriptor desc = IDE.getDefaultEditor(file);
String editorId = desc.getId();
if (editorId.startsWith(AndroidConstants.EDITORS_NAMESPACE)) {
// reset the default editor.
IDE.setDefaultEditor(file, null);
}
}
}
}
}
}
private void resourceAdded(IFile file, ResourceFolderType type) {
// set the default editor based on the type.
if (type == ResourceFolderType.LAYOUT) {
IDE.setDefaultEditor(file, LayoutEditor.ID);
} else if (type == ResourceFolderType.DRAWABLE
|| type == ResourceFolderType.VALUES) {
IDE.setDefaultEditor(file, ResourcesEditor.ID);
} else if (type == ResourceFolderType.MENU) {
IDE.setDefaultEditor(file, MenuEditor.ID);
} else if (type == ResourceFolderType.XML) {
if (XmlEditor.canHandleFile(file)) {
IDE.setDefaultEditor(file, XmlEditor.ID);
} else {
// set a property to determine later if the XML can be handled
QualifiedName qname = new QualifiedName(
AdtPlugin.PLUGIN_ID,
UNKNOWN_EDITOR);
try {
file.setPersistentProperty(qname, "1");
} catch (CoreException e) {
// pass
}
}
}
}
private void resourceChanged(IFile file, ResourceFolderType type) {
if (type == ResourceFolderType.XML) {
IEditorDescriptor ed = IDE.getDefaultEditor(file);
if (ed == null || ed.getId() != XmlEditor.ID) {
QualifiedName qname = new QualifiedName(
AdtPlugin.PLUGIN_ID,
UNKNOWN_EDITOR);
String prop = null;
try {
prop = file.getPersistentProperty(qname);
} catch (CoreException e) {
// pass
}
if (prop != null && XmlEditor.canHandleFile(file)) {
try {
// remove the property & set editor
file.setPersistentProperty(qname, null);
IWorkbenchPage page = PlatformUI.getWorkbench().
getActiveWorkbenchWindow().getActivePage();
IEditorPart oldEditor = page.findEditor(new FileEditorInput(file));
if (oldEditor != null &&
AdtPlugin.displayPrompt("Android XML Editor",
String.format("The file you just saved as been recognized as a file that could be better handled using the Android XML Editor. Do you want to edit '%1$s' using the Android XML editor instead?",
file.getFullPath()))) {
IDE.setDefaultEditor(file, XmlEditor.ID);
IEditorPart newEditor = page.openEditor(
new FileEditorInput(file),
XmlEditor.ID,
true, /* activate */
IWorkbenchPage.MATCH_NONE);
if (newEditor != null) {
page.closeEditor(oldEditor, true /* save */);
}
}
} catch (CoreException e) {
// setPersistentProperty or page.openEditor may have failed
}
}
}
}
}
}, IResourceDelta.ADDED | IResourceDelta.CHANGED);
}
public void addResourceChangedListener(Runnable resourceRefreshListener) {
mResourceRefreshListener.add(resourceRefreshListener);
}
public void removeResourceChangedListener(Runnable resourceRefreshListener) {
mResourceRefreshListener.remove(resourceRefreshListener);
}
public static synchronized OutputStream getErrorStream() {
return sPlugin.mAndroidConsoleErrorStream;
}
}

View File

@@ -16,9 +16,8 @@
package com.android.ide.eclipse.adt;
import com.android.ddmlib.Device;
import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.sdklib.SdkConstants;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
@@ -42,19 +41,6 @@ import java.util.regex.Pattern;
*
*/
final class VersionCheck {
/** Pattern to get the SDK build incremental version from the
* <code>$SDK/tools/lib/build.prop file</code>. */
private final static Pattern sBuildVersionPattern = Pattern.compile(
"^" + Device.PROP_BUILD_VERSION + "=(.+)$"); //$NON-NLS-1$
/**
* Pattern to parse release type SDK version number. This parses the content read with
* <code>sBuildIdPattern</code>.
*/
private final static Pattern sSdkVersionPattern = Pattern.compile(
"^(\\d+)\\.(\\d+)_r(\\d+)$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
/**
* Pattern to get the minimum plugin version supported by the SDK. This is read from
* the file <code>$SDK/tools/lib/plugin.prop</code>.
@@ -69,58 +55,7 @@ final class VersionCheck {
*/
public static boolean checkVersion(String osSdkPath, CheckSdkErrorHandler errorHandler) {
AdtPlugin plugin = AdtPlugin.getDefault();
String osLibs = osSdkPath + AndroidConstants.OS_SDK_LIBS_FOLDER;
/*
* All plugins should work with all SDKs. Newer SDKs may require a newer plugin
* but this is handled below.
* Still, we need to grab the SDK version from this file. This is used
* to compare to running emulator/device when launching run/debug sessions.
*/
try {
FileReader reader = new FileReader(osLibs + AndroidConstants.FN_BUILD_PROP);
BufferedReader bReader = new BufferedReader(reader);
String line;
while ((line = bReader.readLine()) != null) {
Matcher m = sBuildVersionPattern.matcher(line);
if (m.matches()) {
plugin.mSdkApiVersion = m.group(1).trim();
/*
* No checks on the version at the moment.
*/
/*
if (plugin.mSdkBuildVersion != null) {
// attempt to get version number from the build id
m = sSdkVersionPattern.matcher(plugin.mSdkBuildVersion);
if (m.matches()) {
// get the platform version number
int platformMajor = Integer.parseInt(m.group(1));
int platformMinor = Integer.parseInt(m.group(2));
@SuppressWarnings("unused") //$NON-NLS-1$
int sdkRelease = Integer.parseInt(m.group(3));
if (platformMajor != 0 || platformMinor != 9) {
return errorHandler.handleError(String.format(
"This version of ADT requires the Android SDK version 0.9\n\nCurrent version is %1$s.\n\nPlease update your SDK to the latest version.",
plugin.mSdkBuildVersion));
}
} else {
// unknown version format.
AdtPlugin.printErrorToConsole(
(Object)String.format(Messages.VersionCheck_Unable_To_Parse_Version_s,
plugin.mSdkBuildVersion));
}
}
*/
break;
}
}
} catch (FileNotFoundException e) {
// the build id will be null, and this is handled by the builders.
} catch (IOException e) {
// the build id will be null, and this is handled by the builders.
}
String osLibs = osSdkPath + SdkConstants.OS_SDK_TOOLS_LIB_FOLDER;
// get the plugin property file, and grab the minimum plugin version required
// to work with the sdk
@@ -128,7 +63,7 @@ final class VersionCheck {
int minMinorVersion = -1;
int minMicroVersion = -1;
try {
FileReader reader = new FileReader(osLibs + AndroidConstants.FN_PLUGIN_PROP);
FileReader reader = new FileReader(osLibs + SdkConstants.FN_PLUGIN_PROP);
BufferedReader bReader = new BufferedReader(reader);
String line;
while ((line = bReader.readLine()) != null) {

View File

@@ -19,6 +19,8 @@ package com.android.ide.eclipse.adt.build;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.sdk.LoadStatus;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.jarutils.DebugKeyProvider;
@@ -28,6 +30,7 @@ import com.android.jarutils.DebugKeyProvider.IKeyGenOutput;
import com.android.jarutils.DebugKeyProvider.KeytoolException;
import com.android.jarutils.SignedJarBuilder.IZipEntryFilter;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
@@ -44,6 +47,7 @@ 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.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
@@ -296,6 +300,15 @@ public class ApkBuilder extends BaseBuilder {
saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
// At this point, we can abort the build if we have to, as we have computed
// our resource delta and stored the result.
// check if we have finished loading the SDK.
if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) {
// we exit silently
return referencedProjects;
}
// Now check the compiler compliance level, not displaying the error
// message since this is not the first builder.
if (ProjectHelper.checkCompilerCompliance(getProject())
@@ -502,7 +515,8 @@ public class ApkBuilder extends BaseBuilder {
commandArray.add(osAssetsPath);
}
commandArray.add("-I"); //$NON-NLS-1$
commandArray.add(AdtPlugin.getOsAbsoluteFramework());
commandArray.add(
Sdk.getCurrent().getTarget(project).getPath(IAndroidTarget.ANDROID_JAR));
commandArray.add("-F"); //$NON-NLS-1$
commandArray.add(osOutFilePath);
@@ -584,16 +598,9 @@ public class ApkBuilder extends BaseBuilder {
DexWrapper wrapper = DexWrapper.getWrapper();
if (wrapper == null) {
if (DexWrapper.getStatus() == DexWrapper.LoadStatus.FAILED) {
if (DexWrapper.getStatus() == LoadStatus.FAILED) {
throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
Messages.ApkBuilder_UnableBuild_Dex_Not_loaded));
} else {
// means we haven't loaded the dex jar yet.
// We set the project to be recompiled after dex is loaded.
AdtPlugin.getDefault().addPostDexProject(javaProject);
// and we exit silently
return false;
}
}
@@ -677,7 +684,7 @@ public class ApkBuilder extends BaseBuilder {
}
// TODO: get the store type from somewhere else.
DebugKeyProvider provider = new DebugKeyProvider(null /* storeType */,
DebugKeyProvider provider = new DebugKeyProvider(osKeyPath, null /* storeType */,
new IKeyGenOutput() {
public void err(String message) {
AdtPlugin.printErrorToConsole(javaProject.getProject(),
@@ -747,9 +754,18 @@ public class ApkBuilder extends BaseBuilder {
}
}
// now write the native libraries.
// First look if the lib folder is there.
IResource libFolder = javaProject.getProject().findMember(
AndroidConstants.FD_NATIVE_LIBS);
if (libFolder != null && libFolder.exists() &&
libFolder.getType() == IResource.FOLDER) {
// look inside and put .so in lib/* by keeping the relative folder path.
writeNativeLibraries(libFolder.getFullPath().segmentCount(), builder, libFolder);
}
// close the jar file and write the manifest and sign it.
builder.close();
} catch (GeneralSecurityException e1) {
// mark project and return
String msg = String.format(Messages.Final_Archive_Error_s, e1.getMessage());
@@ -784,6 +800,12 @@ public class ApkBuilder extends BaseBuilder {
// and also output it in the console
AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
} catch (CoreException e) {
// mark project and return
String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage());
AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
return false;
} finally {
if (fos != null) {
try {
@@ -797,6 +819,48 @@ public class ApkBuilder extends BaseBuilder {
return true;
}
/**
* Writes native libraries into a {@link SignedJarBuilder}.
* <p/>This recursively go through folder and writes .so files.
* The path in the archive is based on the root folder containing the libraries in the project.
* Its segment count is passed to the method to compute the resources path relative to the root
* folder.
* Native libraries in the archive must be in a "lib" folder. Everything in the project native
* lib folder directly goes in this "lib" folder in the archive.
*
*
* @param rooSegmentCount The number of segment of the path of the folder containing the
* libraries. This is used to compute the path in the archive.
* @param jarBuilder the {@link SignedJarBuilder} used to create the archive.
* @param resource the IResource to write.
* @throws CoreException
* @throws IOException
*/
private void writeNativeLibraries(int rootSegmentCount, SignedJarBuilder jarBuilder,
IResource resource) throws CoreException, IOException {
if (resource.getType() == IResource.FILE) {
IPath path = resource.getFullPath();
// check the extension.
if (path.getFileExtension().equalsIgnoreCase(AndroidConstants.EXT_NATIVE_LIB)) {
// remove the first segment to build the path inside the archive.
path = path.removeFirstSegments(rootSegmentCount);
// add it to the archive.
IPath apkPath = new Path(AndroidConstants.FD_APK_NATIVE_LIBS);
apkPath = apkPath.append(path);
// writes the file in the apk.
jarBuilder.writeFile(resource.getLocation().toFile(), apkPath.toString());
}
} else if (resource.getType() == IResource.FOLDER) {
IResource[] members = ((IFolder)resource).members();
for (IResource member : members) {
writeNativeLibraries(rootSegmentCount, jarBuilder, member);
}
}
}
/**
* Writes the standard resources of a project and its referenced projects
* into a {@link SignedJarBuilder}.

View File

@@ -42,6 +42,7 @@ import java.util.ArrayList;
* <li>Any change to the classes.dex inside the output folder</li>
* <li>Any change to the packaged resources file inside the output folder</li>
* <li>Any change to a non java/aidl file inside the source folders</li>
* <li>Any change to .so file inside the lib (native library) folder</li>
* </ul>
*/
public class ApkDeltaVisitor extends BaseDeltaVisitor
@@ -79,6 +80,8 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
private IPath mResPath;
private IPath mLibFolder;
/**
* Builds the object with a specified output folder.
* @param builder the xml builder using this object to visit the
@@ -104,6 +107,11 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
if (resFolder != null) {
mResPath = resFolder.getFullPath();
}
IResource libFolder = builder.getProject().findMember(AndroidConstants.FD_NATIVE_LIBS);
if (libFolder != null) {
mLibFolder = libFolder.getFullPath();
}
}
public boolean getConvertToDex() {
@@ -161,6 +169,7 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
// check the other folders.
if (mOutputPath != null && mOutputPath.isPrefixOf(path)) {
// a resource changed inside the output folder.
if (type == IResource.FILE) {
// just check this is a .class file. Any modification will
// trigger a change in the classes.dex file
@@ -217,6 +226,17 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
mPackageResources = true;
mMakeFinalPackage = true;
return false;
} else if (mLibFolder != null && mLibFolder.isPrefixOf(path)) {
// inside the native library folder. Test if the changed resource is a .so file.
if (type == IResource.FILE &&
path.getFileExtension().equalsIgnoreCase(AndroidConstants.EXT_NATIVE_LIB)) {
mMakeFinalPackage = true;
return false; // return false for file.
}
// for folders, return true only if we don't already know we have to make the
// final package.
return mMakeFinalPackage == false;
} else {
// we are in a folder that is neither the resource folders, nor the output.
// check against all the source folders, unless we already know we need to do

View File

@@ -806,12 +806,6 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
// get the IPath
IPath path = e.getPath();
// get the file name. if it's the framework jar, we ignore that file.
// since we now use classpath container, this is here for legacy purpose only.
if (AndroidConstants.FN_FRAMEWORK_LIBRARY.equals(path.lastSegment())) {
continue;
}
// check the name ends with .jar
if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
boolean local = false;

View File

@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.build;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.LoadStatus;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
@@ -47,9 +48,6 @@ public final class DexWrapper {
private static DexWrapper sWrapper;
/** Status for the Loading of the dex jar file */
public enum LoadStatus { LOADING, LOADED, FAILED }
private static LoadStatus sLoadStatus = LoadStatus.LOADING;
private Method mRunMethod;

View File

@@ -18,14 +18,16 @@ package com.android.ide.eclipse.adt.build;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor;
import com.android.ide.eclipse.adt.project.FixLaunchConfig;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.sdk.LoadStatus;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestHelper;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.ide.eclipse.common.project.XmlErrorHandler.BasicXmlErrorListener;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
@@ -44,7 +46,6 @@ import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.ui.ide.IDE;
import java.io.IOException;
import java.util.ArrayList;
@@ -130,7 +131,6 @@ public class PreCompilerBuilder extends BaseBuilder {
mSource);
try {
mNewFile.setDerived(true);
IDE.setDefaultEditor(mNewFile, ReadOnlyJavaEditor.ID);
} catch (CoreException e) {
// This really shouldn't happen since we check that the resource exist.
// Worst case scenario, the resource isn't marked as derived.
@@ -266,8 +266,14 @@ public class PreCompilerBuilder extends BaseBuilder {
// record the state
mCompileResources |= dv.getCompileResources();
mergeAidlFileModifications(dv.getAidlToCompile(),
dv.getAidlToRemove());
// handle aidl modification
if (dv.getFullAidlRecompilation()) {
buildAidlCompilationList(project, sourceList);
} else {
mergeAidlFileModifications(dv.getAidlToCompile(),
dv.getAidlToRemove());
}
// if there was some XML errors, we just return w/o doing
// anything since we've put some markers in the files anyway.
@@ -290,6 +296,12 @@ public class PreCompilerBuilder extends BaseBuilder {
// At this point we have stored what needs to be build, so we can
// do some high level test and abort if needed.
// check if we have finished loading the SDK.
if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) {
// we exit silently
return null;
}
// check the compiler compliance level, not displaying the error message
// since this is not the first builder.
if (ProjectHelper.checkCompilerCompliance(getProject())
@@ -302,13 +314,19 @@ public class PreCompilerBuilder extends BaseBuilder {
// Check that the SDK directory has been setup.
String osSdkFolder = AdtPlugin.getOsSdkFolder();
if (osSdkFolder.length() == 0) {
if (osSdkFolder == null || osSdkFolder.length() == 0) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
Messages.No_SDK_Setup_Error);
markProject(AdtConstants.MARKER_ADT, Messages.No_SDK_Setup_Error,
IMarker.SEVERITY_ERROR);
return null;
}
IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project);
if (projectTarget == null) {
// no target. error has been output by the container initializer: exit silently.
return null;
}
// get the manifest file
IFile manifest = AndroidManifestHelper.getManifest(project);
@@ -448,7 +466,7 @@ public class PreCompilerBuilder extends BaseBuilder {
array.add("-S"); //$NON-NLS-1$
array.add(osResPath);
array.add("-I"); //$NON-NLS-1$
array.add(AdtPlugin.getOsAbsoluteFramework());
array.add(projectTarget.getPath(IAndroidTarget.ANDROID_JAR));
if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) {
StringBuilder sb = new StringBuilder();
@@ -563,6 +581,8 @@ public class PreCompilerBuilder extends BaseBuilder {
deleteObsoleteGeneratedClass(AndroidConstants.FN_MANIFEST_CLASS,
mManifestPackageSourceFolder, mManifestPackage);
}
// FIXME: delete all java generated from aidl.
}
@Override
@@ -736,12 +756,14 @@ public class PreCompilerBuilder extends BaseBuilder {
if (mAidlToCompile.size() == 0 && mAidlToRemove.size() == 0) {
return false;
}
// create the command line
String[] command = new String[4 + sourceFolders.size() + (folderAidlPath != null ? 1 : 0)];
int index = 0;
int aidlIndex;
command[index++] = AdtPlugin.getOsAbsoluteAidl();
command[index++] = "-p" + AdtPlugin.getOsAbsoluteFrameworkAidl(); //$NON-NLS-1$
command[aidlIndex = index++] = "-p"; //$NON-NLS-1$
if (folderAidlPath != null) {
command[index++] = "-p" + folderAidlPath; //$NON-NLS-1$
}
@@ -813,6 +835,8 @@ public class PreCompilerBuilder extends BaseBuilder {
prepareFileForExternalModification(javaFile);
// finish to set the command line.
command[aidlIndex] = "-p" + Sdk.getCurrent().getTarget(aidlFile.getProject()).getPath(
IAndroidTarget.ANDROID_AIDL); //$NON-NLS-1$
command[index] = osPath;
command[index + 1] = osJavaPath;
@@ -922,7 +946,8 @@ public class PreCompilerBuilder extends BaseBuilder {
}
/**
* Goes through the buildpath and fills the list of aidl files to compile.
* Goes through the build paths and fills the list of aidl files to compile
* ({@link #mAidlToCompile}).
* @param project The project.
* @param buildPaths The list of build paths.
*/
@@ -977,7 +1002,7 @@ public class PreCompilerBuilder extends BaseBuilder {
scanContainerForAidl((IFolder)r);
break;
default:
// this would mean it's a project or the workspaceroot
// this would mean it's a project or the workspace root
// which is unlikely to happen. we do nothing
break;
}

View File

@@ -42,6 +42,7 @@ import java.util.ArrayList;
class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
IResourceDeltaVisitor {
// Result fields.
/**
* Compile flag. This is set to true if one of the changed/added/removed
* file is a resource file. Upon visiting all the delta resources, if
@@ -50,6 +51,22 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
*/
private boolean mCompileResources = false;
/** List of .aidl files found that are modified or new. */
private final ArrayList<IFile> mAidlToCompile = new ArrayList<IFile>();
/** List of .aidl files that have been removed. */
private final ArrayList<IFile> mAidlToRemove = new ArrayList<IFile>();
/** Aidl forced recompilation flag. This is set to true if project.aidl is modified. */
private boolean mFullAidlCompilation = false;
/** Manifest check/parsing flag. */
private boolean mCheckedManifestXml = false;
/** Application Pacakge, gathered from the parsing of the manifest */
private String mJavaPackage = null;
// Internal usage fields.
/**
* In Resource folder flag. This allows us to know if we're in the
* resource folder.
@@ -65,14 +82,6 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
/** List of source folders. */
private ArrayList<IPath> mSourceFolders;
/** List of .aidl files found that are modified or new. */
private final ArrayList<IFile> mAidlToCompile = new ArrayList<IFile>();
/** List of .aidl files that have been removed. */
private final ArrayList<IFile> mAidlToRemove = new ArrayList<IFile>();
private boolean mCheckedManifestXml = false;
private String mJavaPackage = null;
public PreCompilerDeltaVisitor(BaseBuilder builder, ArrayList<IPath> sourceFolders) {
super(builder);
@@ -90,6 +99,10 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
public ArrayList<IFile> getAidlToRemove() {
return mAidlToRemove;
}
public boolean getFullAidlRecompilation() {
return mFullAidlCompilation;
}
/**
* Returns whether the manifest file was parsed/checked for error during the resource delta
@@ -100,7 +113,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
}
/**
* Returns the manifest package if the manifest was checked.
* Returns the manifest package if the manifest was checked/parsed.
* <p/>
* This can return null in two cases:
* <ul>
@@ -169,11 +182,14 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
// we don't want to go to the children, not like they are
// any for this resource anyway.
return false;
} else if (AndroidConstants.FN_PROJECT_AIDL.equalsIgnoreCase(segments[1])) {
// need to force recompilation of all the aidl files
mFullAidlCompilation = true;
}
}
// at this point we can either be in the source folder or in the
// resource foler or in a different folder that contains a source
// resource folder or in a different folder that contains a source
// folder.
// This is due to not all source folder being src/. Some could be
// something/somethingelse/src/
@@ -227,7 +243,8 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
PreCompilerBuilder.PROPERTY_ANDROID_CONFLICT, true);
}
// we add it anyway so that we can try to compile it every time.
// we add it anyway so that we can try to compile it at every compilation
// until the conflict is fixed.
mAidlToCompile.add(file);
} else {
@@ -245,6 +262,8 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
boolean outputMessage = false;
// Special case of R.java/Manifest.java.
// FIXME: This does not check the package. Any modification of R.java/Manifest.java in another project will trigger a new recompilation of the resources.
if (AndroidConstants.FN_RESOURCE_CLASS.equals(fileName) ||
AndroidConstants.FN_MANIFEST_CLASS.equals(fileName)) {
// if it was removed, there's a possibility that it was removed due to a

View File

@@ -62,13 +62,10 @@ public class ResourceManagerBuilder extends IncrementalProjectBuilder {
switch (res) {
case ProjectHelper.COMPILER_COMPLIANCE_LEVEL:
errorMessage = Messages.Requires_Compiler_Compliance_5;
return null;
case ProjectHelper.COMPILER_COMPLIANCE_SOURCE:
errorMessage = Messages.Requires_Source_Compatibility_5;
return null;
case ProjectHelper.COMPILER_COMPLIANCE_CODEGEN_TARGET:
errorMessage = Messages.Requires_Class_Compatibility_5;
return null;
}
if (errorMessage != null) {

View File

@@ -30,9 +30,9 @@ import com.android.ddmlib.SyncService.SyncResult;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.debug.launching.DeviceChooserDialog.DeviceChooserResponse;
import com.android.ide.eclipse.adt.debug.ui.EmulatorConfigTab;
import com.android.ide.eclipse.adt.debug.ui.SkinRepository;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.common.project.AndroidManifestHelper;
import com.android.sdklib.SdkConstants;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
@@ -102,6 +102,9 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
/** Debuggable attribute of the manifest file. */
Boolean mDebuggable = null;
/** Required ApiVersionNumber by the app. 0 means no requirements */
int mRequiredApiVersionNumber = 0;
InstallRetryMode mRetryMode = InstallRetryMode.NEVER;
/**
@@ -126,8 +129,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
/** Basic constructor with activity and package info. */
public DelayedLaunchInfo(IProject project, String packageName, String activity,
IFile pack, Boolean debuggable, int launchAction, AndroidLaunch launch,
IProgressMonitor monitor) {
IFile pack, Boolean debuggable, int requiredApiVersionNumber, int launchAction,
AndroidLaunch launch, IProgressMonitor monitor) {
mProject = project;
mPackageName = packageName;
mActivity = activity;
@@ -136,6 +139,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
mLaunch = launch;
mMonitor = monitor;
mDebuggable = debuggable;
mRequiredApiVersionNumber = requiredApiVersionNumber;
}
}
@@ -256,13 +260,10 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
try {
mSkin = config.getAttribute(LaunchConfigDelegate.ATTR_SKIN, mSkin);
if (mSkin == null) {
mSkin = SkinRepository.getInstance().checkSkin(
LaunchConfigDelegate.DEFAULT_SKIN);
} else {
mSkin = SkinRepository.getInstance().checkSkin(mSkin);
mSkin = SdkConstants.SKIN_DEFAULT;
}
} catch (CoreException e) {
mSkin = SkinRepository.getInstance().checkSkin(LaunchConfigDelegate.DEFAULT_SKIN);
mSkin = SdkConstants.SKIN_DEFAULT;
}
int index = LaunchConfigDelegate.DEFAULT_SPEED;
@@ -539,7 +540,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
LaunchConfigDelegate.DEFAULT_DELAY);
// default skin
wc.setAttribute(LaunchConfigDelegate.ATTR_SKIN, LaunchConfigDelegate.DEFAULT_SKIN);
wc.setAttribute(LaunchConfigDelegate.ATTR_SKIN, SdkConstants.SKIN_DEFAULT);
// default wipe data mode
wc.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA,
@@ -599,13 +600,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
* defined by <code>ILaunchManager</code> - <code>RUN_MODE</code> or
* <code>DEBUG_MODE</code>.
* @param apk the resource to the apk to launch.
* @param debuggable
* @param debuggable the debuggable value of the app, or null if not set.
* @param requiredApiVersionNumber the api version required by the app, or -1 if none.
* @param activity the class to provide to am to launch
* @param config the launch configuration
* @param launch the launch object
*/
public void launch(final IProject project, String mode, IFile apk,
String packageName, Boolean debuggable, String activity,
String packageName, Boolean debuggable, int requiredApiVersionNumber, String activity,
final AndroidLaunchConfiguration config, final AndroidLaunch launch,
IProgressMonitor monitor) {
@@ -619,7 +621,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
// create the launch info
final DelayedLaunchInfo launchInfo = new DelayedLaunchInfo(project, packageName,
activity, apk, debuggable, config.mLaunchAction, launch, monitor);
activity, apk, debuggable, requiredApiVersionNumber, config.mLaunchAction,
launch, monitor);
// set the debug mode
launchInfo.mDebugMode = mode.equals(ILaunchManager.DEBUG_MODE);
@@ -711,6 +714,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
// stop the launch and return
mWaitingForEmulatorLaunches.remove(launchInfo);
AdtPlugin.printErrorToConsole(project, "Launch canceled!");
launch.stopLaunch();
return;
}
@@ -761,31 +765,63 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
}
}
private void checkBuildInfo(DelayedLaunchInfo launchInfo, Device device) {
/**
* Checks the build information, and returns whether the launch should continue.
* <p/>The value tested are:
* <ul>
* <li>Minimum API version requested by the application. If the target device does not match,
* the launch is canceled.</li>
* <li>Debuggable attribute of the application and whether or not the device requires it. If
* the device requires it and it is not set in the manifest, the launch will be forced to
* "release" mode instead of "debug"</li>
* <ul>
* @param launchInfo
* @param device
* @return
*/
private boolean checkBuildInfo(DelayedLaunchInfo launchInfo, Device device) {
if (device != null) {
// get the SDK build
String sdkBuild = AdtPlugin.getSdkApiVersion();
// can only complain if the sdkBuild is known
if (sdkBuild != null) {
String deviceVersion = device.getProperty(Device.PROP_BUILD_VERSION);
if (deviceVersion == null) {
AdtPlugin.printToConsole(launchInfo.mProject, "WARNING: Unknown device API version!");
} else {
if (sdkBuild.equals(deviceVersion) == false) {
// TODO do a proper check, including testing the content of the uses-sdk string in the manifest to detect real incompatibility.
String msg = String.format(
"WARNING: Device API version (%1$s) does not match SDK API version (%2$s)",
deviceVersion, sdkBuild);
AdtPlugin.printErrorToConsole(launchInfo.mProject, msg);
}
}
} else {
AdtPlugin.printToConsole(launchInfo.mProject, "WARNING: Unknown SDK API version!");
// check the app required API level versus the target device API level
String deviceApiVersionName = device.getProperty(Device.PROP_BUILD_VERSION);
String value = device.getProperty(Device.PROP_BUILD_VERSION_NUMBER);
int deviceApiVersionNumber = 0;
try {
deviceApiVersionNumber = Integer.parseInt(value);
} catch (NumberFormatException e) {
// pass, we'll keep the deviceVersionNumber value at 0.
}
if (launchInfo.mRequiredApiVersionNumber == 0) {
// warn the API level requirement is not set.
AdtPlugin.printErrorToConsole(launchInfo.mProject,
"WARNING: Application does not specify an API level requirement!");
// and display the target device API level (if known)
if (deviceApiVersionName == null || deviceApiVersionNumber == 0) {
AdtPlugin.printErrorToConsole(launchInfo.mProject,
"WARNING: Unknown device API version!");
} else {
AdtPlugin.printErrorToConsole(launchInfo.mProject, String.format(
"Device API version is %1$d (Android %2$s)", deviceApiVersionNumber,
deviceApiVersionName));
}
} else { // app requires a specific API level
if (deviceApiVersionName == null || deviceApiVersionNumber == 0) {
AdtPlugin.printToConsole(launchInfo.mProject,
"WARNING: Unknown device API version!");
} else if (deviceApiVersionNumber < launchInfo.mRequiredApiVersionNumber) {
String msg = String.format(
"ERROR: Application requires API version %1$d. Device API version is %2$d (Android %3$s).",
launchInfo.mRequiredApiVersionNumber, deviceApiVersionNumber,
deviceApiVersionName);
AdtPlugin.printErrorToConsole(launchInfo.mProject, msg);
// abort the launch
return false;
}
}
// now checks that the device/app can be debugged (if needed)
if (device.isEmulator() == false && launchInfo.mDebugMode) {
String debuggableDevice = device.getProperty(Device.PROP_DEBUGGABLE);
@@ -818,6 +854,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
}
}
}
return true;
}
/**
@@ -829,10 +867,16 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
* @return true if succeed
*/
private boolean simpleLaunch(DelayedLaunchInfo launchInfo, Device device) {
checkBuildInfo(launchInfo, device);
// API level check
if (checkBuildInfo(launchInfo, device) == false) {
AdtPlugin.printErrorToConsole(launchInfo.mProject, "Launch canceled!");
launchInfo.mLaunch.stopLaunch();
return false;
}
// sync the app
if (syncApp(launchInfo, device) == false) {
AdtPlugin.printErrorToConsole(launchInfo.mProject, "Launch canceled!");
launchInfo.mLaunch.stopLaunch();
return false;
}
@@ -1127,6 +1171,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
} catch (IOException e) {
// something went wrong trying to launch the app.
// lets stop the Launch
AdtPlugin.printErrorToConsole(info.mProject,
String.format("Launch error: %s", e.getMessage()));
info.mLaunch.stopLaunch();
// and remove it from the list of app waiting for debuggers
@@ -1139,25 +1185,20 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
private boolean launchEmulator(AndroidLaunchConfiguration config) {
// split the custom command line in segments
String[] segs;
ArrayList<String> customArgs = new ArrayList<String>();
boolean has_wipe_data = false;
if (config.mEmulatorCommandLine != null && config.mEmulatorCommandLine.length() > 0) {
segs = config.mEmulatorCommandLine.split("\\s+"); //$NON-NLS-1$
String[] segments = config.mEmulatorCommandLine.split("\\s+"); //$NON-NLS-1$
// we need to remove the empty strings
ArrayList<String> array = new ArrayList<String>();
for (String s : segs) {
for (String s : segments) {
if (s.length() > 0) {
array.add(s);
customArgs.add(s);
if (!has_wipe_data && s.equals(FLAG_WIPE_DATA)) {
has_wipe_data = true;
}
}
}
segs = array.toArray(new String[array.size()]);
} else {
segs = new String[0];
}
boolean needs_wipe_data = config.mWipeData && !has_wipe_data;
@@ -1167,30 +1208,38 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
}
}
boolean needs_no_boot_anim = config.mNoBootAnim;
// build the command line based on the available parameters.
ArrayList<String> list = new ArrayList<String>();
list.add(AdtPlugin.getOsAbsoluteEmulator());
if (config.mSkin != null) {
list.add(FLAG_SKIN);
list.add(config.mSkin);
}
if (config.mNetworkSpeed != null) {
list.add(FLAG_NETSPEED);
list.add(config.mNetworkSpeed);
}
if (config.mNetworkDelay != null) {
list.add(FLAG_NETDELAY);
list.add(config.mNetworkDelay);
}
// get the command line
String[] command = new String[7 + segs.length +
(needs_wipe_data ? 1 : 0) +
(needs_no_boot_anim ? 1 : 0)];
int index = 0;
command[index++] = AdtPlugin.getOsAbsoluteEmulator();
command[index++] = FLAG_SKIN; //$NON-NLS-1$
command[index++] = config.mSkin;
command[index++] = FLAG_NETSPEED; //$NON-NLS-1$
command[index++] = config.mNetworkSpeed;
command[index++] = FLAG_NETDELAY; //$NON-NLS-1$
command[index++] = config.mNetworkDelay;
if (needs_wipe_data) {
command[index++] = FLAG_WIPE_DATA;
list.add(FLAG_WIPE_DATA);
}
if (needs_no_boot_anim) {
command[index++] = FLAG_NO_BOOT_ANIM;
}
for (String s : segs) {
command[index++] = s;
if (config.mNoBootAnim) {
list.add(FLAG_NO_BOOT_ANIM);
}
list.addAll(customArgs);
// convert the list into an array for the call to exec.
String[] command = list.toArray(new String[list.size()]);
// launch the emulator
try {
Process process = Runtime.getRuntime().exec(command);
@@ -1278,12 +1327,11 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
/**
* Launch a new thread that connects a remote debugger on the specified port.
* @param debugPort The port to connect the debugger to
* @param launch The associated AndroidLaunch object.
* @param androidLaunch The associated AndroidLaunch object.
* @param monitor A Progress monitor
* @see connectRemoveDebugger()
*/
public static void launchRemoteDebugger(final ILaunchConfiguration config,
final int debugPort, final AndroidLaunch androidLaunch,
public static void launchRemoteDebugger( final int debugPort, final AndroidLaunch androidLaunch,
final IProgressMonitor monitor) {
new Thread("Debugger connection") { //$NON-NLS-1$
@Override
@@ -1342,13 +1390,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
synchronized (sListLock) {
// look if there's an app waiting for a device
if (mWaitingForEmulatorLaunches.size() > 0) {
// remove first item from the list
// get/remove first launch item from the list
// FIXME: what if we have multiple launches waiting?
DelayedLaunchInfo launchInfo = mWaitingForEmulatorLaunches.get(0);
mWaitingForEmulatorLaunches.remove(0);
// give it its device
// give the launch item its device for later use.
launchInfo.mDevice = device;
// and move it to the other list
mWaitingForReadyEmulatorList.add(launchInfo);
@@ -1433,12 +1482,23 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
// look for application waiting for home
synchronized (sListLock) {
boolean foundMatch = false;
for (int i = 0 ; i < mWaitingForReadyEmulatorList.size() ;) {
DelayedLaunchInfo launchInfo = mWaitingForReadyEmulatorList.get(i);
if (launchInfo.mDevice == device) {
// it's match, remove from the list
mWaitingForReadyEmulatorList.remove(i);
// We couldn't check earlier the API level of the device
// (it's asynchronous when the device boot, and usually
// deviceConnected is called before it's queried for its build info)
// so we check now
if (checkBuildInfo(launchInfo, device) == false) {
// device is not the proper API!
AdtPlugin.printErrorToConsole(launchInfo.mProject,
"Launch canceled!");
launchInfo.mLaunch.stopLaunch();
return;
}
AdtPlugin.printToConsole(launchInfo.mProject,
String.format("HOME is up on device '%1$s'",
@@ -1448,16 +1508,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
if (syncApp(launchInfo, device)) {
// application package is sync'ed, lets attempt to launch it.
launchApp(launchInfo, device);
// if we haven't checked the device build info, lets do it here
if (foundMatch == false) {
foundMatch = true;
checkBuildInfo(launchInfo, device);
}
} else {
// failure! Cancel and return
AdtPlugin.printErrorToConsole(launchInfo.mProject,
"Launch canceled!");
launchInfo.mLaunch.stopLaunch();
}
break;
} else {
i++;
}
@@ -1530,6 +1588,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
}
} catch (CoreException e) {
// well something went wrong.
AdtPlugin.printErrorToConsole(launchInfo.mProject,
String.format("Launch error: %s", e.getMessage()));
// stop the launch
launchInfo.mLaunch.stopLaunch();
}

View File

@@ -83,11 +83,6 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
/** Skin to be used to launch the emulator */
public static final String ATTR_SKIN = AdtPlugin.PLUGIN_ID + ".skin"; //$NON-NLS-1$
/**
* Name of the default Skin.
*/
public static final String DEFAULT_SKIN = "HVGA"; //$NON-NLS-1$
public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$
/**
@@ -138,8 +133,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
// if we have a valid debug port, this means we're debugging an app
// that's already launched.
if (debugPort != INVALID_DEBUG_PORT) {
AndroidLaunchController.launchRemoteDebugger(configuration,
debugPort, androidLaunch, monitor);
AndroidLaunchController.launchRemoteDebugger(debugPort, androidLaunch, monitor);
return;
}
@@ -302,7 +296,8 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
// everything seems fine, we ask the launch controller to handle
// the rest
controller.launch(project, mode, applicationPackage, manifestParser.getPackage(),
manifestParser.getDebuggable(), activityName, config, androidLaunch, monitor);
manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(),
activityName, config, androidLaunch, monitor);
}
@Override

View File

@@ -18,13 +18,19 @@ package com.android.ide.eclipse.adt.debug.ui;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate;
import com.android.ide.eclipse.adt.debug.ui.SkinRepository.Skin;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
@@ -82,6 +88,8 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
private Button mNoBootAnimButton;
private IAndroidTarget mTarget;
/**
* Returns the emulator ready speed option value.
* @param value The index of the combo selection.
@@ -169,12 +177,6 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Screen Size:");
mSkinCombo = new Combo(mEmulatorOptionsGroup, SWT.READ_ONLY);
Skin[] skins = SkinRepository.getInstance().getSkins();
if (skins != null) {
for (Skin skin : skins) {
mSkinCombo.add(skin.getDescription());
}
}
mSkinCombo.addSelectionListener(new SelectionAdapter() {
// called when selection changes
@Override
@@ -182,7 +184,6 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
updateLaunchConfigurationDialog();
}
});
mSkinCombo.pack();
// network options
new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Network Speed:");
@@ -287,6 +288,42 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
}
mAutoTargetButton.setSelection(value);
mManualTargetButton.setSelection(!value);
// look for the project name to get its target.
String projectName = "";
try {
projectName = configuration.getAttribute(
IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, projectName);
} catch (CoreException ce) {
}
IProject project = null;
// get the list of existing Android projects from the workspace.
IJavaProject[] projects = BaseProjectHelper.getAndroidProjects();
if (projects != null) {
// look for the project whose name we read from the configuration.
for (IJavaProject p : projects) {
if (p.getElementName().equals(projectName)) {
project = p.getProject();
break;
}
}
}
mSkinCombo.removeAll();
if (project != null) {
mTarget = Sdk.getCurrent().getTarget(project);
if (mTarget != null) {
String[] skins = mTarget.getSkins();
if (skins != null) {
for (String skin : skins) {
mSkinCombo.add(skin);
}
mSkinCombo.pack();
}
}
}
value = LaunchConfigDelegate.DEFAULT_WIPE_DATA;
try {
@@ -307,16 +344,18 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
int index = -1;
try {
String skin = configuration.getAttribute(LaunchConfigDelegate.ATTR_SKIN, (String)null);
if (skin != null) {
index = getSkinIndex(skin);
if (skin == null) {
skin = SdkConstants.SKIN_DEFAULT;
}
index = getSkinIndex(skin);
} catch (CoreException e) {
index = getSkinIndex(SkinRepository.getInstance().checkSkin(
LaunchConfigDelegate.DEFAULT_SKIN));
index = getSkinIndex(SdkConstants.SKIN_DEFAULT);
}
if (index == -1) {
mSkinCombo.clearSelection();
mSkinCombo.select(0);
updateLaunchConfigurationDialog();
} else {
mSkinCombo.select(index);
}
@@ -387,7 +426,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
LaunchConfigDelegate.DEFAULT_TARGET_MODE);
configuration.setAttribute(LaunchConfigDelegate.ATTR_SKIN,
LaunchConfigDelegate.DEFAULT_SKIN);
SdkConstants.SKIN_DEFAULT);
configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED,
LaunchConfigDelegate.DEFAULT_SPEED);
configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY,
@@ -403,11 +442,31 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
}
private String getSkinNameByIndex(int index) {
return SkinRepository.getInstance().getSkinNameByIndex(index);
if (mTarget != null && index > 0) {
String[] skins = mTarget.getSkins();
if (skins != null && index < skins.length) {
return skins[index];
}
}
return null;
}
private int getSkinIndex(String name) {
return SkinRepository.getInstance().getSkinIndex(name);
if (mTarget != null) {
String[] skins = mTarget.getSkins();
if (skins != null) {
int index = 0;
for (String skin : skins) {
if (skin.equalsIgnoreCase(name)) {
return index;
}
index++;
}
}
}
return -1;
}
}

View File

@@ -1,152 +0,0 @@
/*
* 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.debug.ui;
import com.android.ide.eclipse.common.AndroidConstants;
import java.io.File;
import java.util.ArrayList;
/**
* Repository for the emulator skins. This class is responsible for parsing the skin folder.
*/
public class SkinRepository {
private final static SkinRepository sInstance = new SkinRepository();
private Skin[] mSkins;
public static class Skin {
String mName;
public Skin(String name) {
mName = name;
}
public String getName() {
return mName;
}
/**
* Returns the human readable description of the skin.
*/
public String getDescription() {
// TODO: parse the skin and output the description.
return mName;
}
}
/**
* Returns the singleton instance.
*/
public static SkinRepository getInstance() {
return sInstance;
}
/**
* Parse the skin folder and build the skin list.
* @param osPath The path of the skin folder.
*/
public void parseFolder(String osPath) {
File skinFolder = new File(osPath);
if (skinFolder.isDirectory()) {
ArrayList<Skin> skinList = new ArrayList<Skin>();
File[] files = skinFolder.listFiles();
for (File skin : files) {
if (skin.isDirectory()) {
// check for layout file
File layout = new File(skin.getPath() + File.separator
+ AndroidConstants.FN_LAYOUT);
if (layout.isFile()) {
// for now we don't parse the content of the layout and
// simply add the directory to the list.
skinList.add(new Skin(skin.getName()));
}
}
}
mSkins = skinList.toArray(new Skin[skinList.size()]);
} else {
mSkins = new Skin[0];
}
}
public Skin[] getSkins() {
return mSkins;
}
/**
* Returns a valid skin folder name. If <code>skin</code> is valid, then it is returned,
* otherwise the first valid skin name is returned.
* @param skin the Skin name to check
* @return a valid skin name or null if there aren't any.
*/
public String checkSkin(String skin) {
if (mSkins != null) {
for (Skin s : mSkins) {
if (s.getName().equals(skin)) {
return skin;
}
}
if (mSkins.length > 0) {
return mSkins[0].getName();
}
}
return null;
}
/**
* Returns the name of a skin by index.
* @param index The index of the skin to return
* @return the skin name of null if the index is invalid.
*/
public String getSkinNameByIndex(int index) {
if (mSkins != null) {
if (index >= 0 && index < mSkins.length) {
return mSkins[index].getName();
}
}
return null;
}
/**
* Returns the index (0 based) of the skin matching the name.
* @param name The name of the skin to look for.
* @return the index of the skin or -1 if the skin was not found.
*/
public int getSkinIndex(String name) {
if (mSkins != null) {
int count = mSkins.length;
for (int i = 0 ; i < count ; i++) {
Skin s = mSkins[i];
if (s.mName.equals(name)) {
return i;
}
}
}
return -1;
}
}

View File

@@ -1,49 +0,0 @@
/*
* 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.editors.java;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.ui.text.IJavaPartitions;
import org.eclipse.jdt.ui.text.JavaSourceViewerConfiguration;
import org.eclipse.jdt.ui.text.JavaTextTools;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
/**
* Read only java editors. This looks like the regular Java editor, except that it
* prevents editing the files. This is used for automatically generated java classes.
*/
public class ReadOnlyJavaEditor extends AbstractDecoratedTextEditor {
public final static String ID =
"com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor"; //$NON-NLS-1$
public ReadOnlyJavaEditor() {
IPreferenceStore javaUiStore = PreferenceConstants.getPreferenceStore();
JavaTextTools jtt = new JavaTextTools(javaUiStore);
JavaSourceViewerConfiguration jsvc = new JavaSourceViewerConfiguration(
jtt.getColorManager(), javaUiStore, this, IJavaPartitions.JAVA_PARTITIONING);
setSourceViewerConfiguration(jsvc);
}
@Override
public boolean isEditable() {
return false;
}
}

View File

@@ -18,6 +18,9 @@ package com.android.ide.eclipse.adt.preferences;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.jarutils.DebugKeyProvider;
import com.android.jarutils.DebugKeyProvider.KeytoolException;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
@@ -29,6 +32,13 @@ import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Date;
/**
* Preference page for build options.
*
@@ -75,7 +85,7 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements
addField(new ReadOnlyFieldEditor(AdtPlugin.PREFS_DEFAULT_DEBUG_KEYSTORE,
Messages.BuildPreferencePage_Default_KeyStore, getFieldEditorParent()));
addField(new FileFieldEditor(AdtPlugin.PREFS_CUSTOM_DEBUG_KEYSTORE,
addField(new KeystoreFieldEditor(AdtPlugin.PREFS_CUSTOM_DEBUG_KEYSTORE,
Messages.BuildPreferencePage_Custom_Keystore, getFieldEditorParent()));
}
@@ -105,4 +115,103 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements
control.setEditable(false);
}
}
/**
* Custom {@link FileFieldEditor} that checks that the keystore is valid.
*/
private static class KeystoreFieldEditor extends FileFieldEditor {
public KeystoreFieldEditor(String name, String label, Composite parent) {
super(name, label, parent);
setValidateStrategy(VALIDATE_ON_KEY_STROKE);
}
@Override
protected boolean checkState() {
String fileName = getTextControl().getText();
fileName = fileName.trim();
// empty values are considered ok.
if (fileName.length() > 0) {
File file = new File(fileName);
if (file.isFile()) {
// attempt to load the debug key.
try {
DebugKeyProvider provider = new DebugKeyProvider(fileName,
null /* storeType */, null /* key gen output */);
PrivateKey key = provider.getDebugKey();
X509Certificate certificate = (X509Certificate)provider.getCertificate();
if (key == null || certificate == null) {
showErrorMessage("Unable to find debug key in keystore!");
return false;
}
Date today = new Date();
if (certificate.getNotAfter().compareTo(today) < 0) {
showErrorMessage("Certificate is expired!");
return false;
}
if (certificate.getNotBefore().compareTo(today) > 0) {
showErrorMessage("Certificate validity is in the future!");
return false;
}
// we're good!
clearErrorMessage();
return true;
} catch (GeneralSecurityException e) {
handleException(e);
return false;
} catch (IOException e) {
handleException(e);
return false;
} catch (KeytoolException e) {
handleException(e);
return false;
} catch (AndroidLocationException e) {
handleException(e);
return false;
}
} else {
// file does not exist.
showErrorMessage("Not a valid keystore path.");
return false; // Apply/OK must be disabled
}
}
clearErrorMessage();
return true;
}
@Override
public Text getTextControl(Composite parent) {
setValidateStrategy(VALIDATE_ON_KEY_STROKE);
return super.getTextControl(parent);
}
/**
* Set the error message from a {@link Throwable}. If the exception has no message, try
* to get the message from the cause.
* @param t the Throwable.
*/
private void handleException(Throwable t) {
String msg = t.getMessage();
if (t == null) {
Throwable cause = t.getCause();
if (cause != null) {
handleException(cause);
} else {
setErrorMessage("Uknown error when getting the debug key!");
}
return;
}
// valid text, display it.
showErrorMessage(msg);
}
}
}

View File

@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.project;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.common.AndroidConstants;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
@@ -117,7 +118,7 @@ public class CreateAidlImportAction implements IObjectActionDelegate {
// create the file with the parcelables
if (parcelables.size() > 0) {
IPath path = project.getLocation();
path = path.append("/project.aidl"); //$NON-NLS-1$
path = path.append(AndroidConstants.FN_PROJECT_AIDL);
File f = new File(path.toOSString());
if (f.exists() == false) {
@@ -194,7 +195,7 @@ public class CreateAidlImportAction implements IObjectActionDelegate {
IType[] superInterfaces = typeHierarchy.getAllSuperInterfaces(type);
for (IType superInterface : superInterfaces) {
if ("android.os.Parcelable".equals(superInterface.getFullyQualifiedName())) { //$NON-NLS-1$
if (AndroidConstants.CLASS_PARCELABLE.equals(superInterface.getFullyQualifiedName())) {
parcelables.add(type.getFullyQualifiedName());
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.project;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.common.AndroidConstants;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ILightweightLabelDecorator;
/**
* A {@link ILabelDecorator} associated with an org.eclipse.ui.decorators extension.
* This is used to add android icons in some special folders in the package explorer.
*/
public class FolderDecorator implements ILightweightLabelDecorator {
private ImageDescriptor mDescriptor;
public FolderDecorator() {
mDescriptor = AdtPlugin.getImageDescriptor("/icons/android_project.png");
}
public void decorate(Object element, IDecoration decoration) {
if (element instanceof IFolder) {
IFolder folder = (IFolder)element;
// get the project and make sure this is an android project
IProject project = folder.getProject();
try {
if (project.hasNature(AndroidConstants.NATURE)) {
// check the folder is directly under the project.
if (folder.getParent().getType() == IResource.PROJECT) {
String name = folder.getName();
if (name.equals(AndroidConstants.FD_ASSETS)) {
decorate(decoration, " [Android assets]");
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
} else if (name.equals(AndroidConstants.FD_RESOURCES)) {
decorate(decoration, " [Android resources]");
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
} else if (name.equals(AndroidConstants.FD_NATIVE_LIBS)) {
decorate(decoration, " [Native Libraries]");
}
}
}
} catch (CoreException e) {
// log the error
AdtPlugin.log(e, "Unable to get nature of project '%s'.", project.getName());
}
}
}
public void decorate(IDecoration decoration, String suffix) {
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
// this is broken as it changes the color of the whole text, not only of the decoration.
// TODO: figure out how to change the color of the decoration only.
// decoration.addSuffix(suffix);
// ITheme theme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme();
// ColorRegistry registry = theme.getColorRegistry();
// decoration.setForegroundColor(registry.get("org.eclipse.jdt.ui.ColoredLabels.decorations"));
}
public boolean isLabelProperty(Object element, String property) {
// at this time return false.
return false;
}
public void addListener(ILabelProviderListener listener) {
// No state change will affect the rendering.
}
public void removeListener(ILabelProviderListener listener) {
// No state change will affect the rendering.
}
public void dispose() {
// nothind to dispose
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.project;
import com.android.ide.eclipse.editors.wizards.NewXmlFileWizard;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPart;
public class NewXmlFileWizardAction implements IObjectActionDelegate {
private ISelection mSelection;
private IWorkbench mWorkbench;
/**
* @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
*/
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
mWorkbench = targetPart.getSite().getWorkbenchWindow().getWorkbench();
}
public void run(IAction action) {
if (mSelection instanceof IStructuredSelection) {
IStructuredSelection selection = (IStructuredSelection)mSelection;
// call the new xml file wizard on the current selection.
NewXmlFileWizard wizard = new NewXmlFileWizard();
wizard.init(mWorkbench, selection);
WizardDialog dialog = new WizardDialog(mWorkbench.getDisplay().getActiveShell(),
wizard);
dialog.open();
}
}
public void selectionChanged(IAction action, ISelection selection) {
this.mSelection = selection;
}
}

View File

@@ -33,7 +33,6 @@ import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
@@ -177,31 +176,6 @@ public final class ProjectHelper {
return -1;
}
/**
* Check the validity of the javadoc attributes in a classpath entry.
* @param frameworkEntry the classpath entry to check.
* @return true if the javadoc attributes is valid, false otherwise.
*/
public static boolean checkJavaDocPath(IClasspathEntry frameworkEntry) {
// get the list of extra attributes
IClasspathAttribute[] attributes = frameworkEntry.getExtraAttributes();
// and search for the one about the javadoc
for (IClasspathAttribute att : attributes) {
if (IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME.
equals(att.getName())) {
// we found a javadoc attribute. Now we test its value.
// get the expect value
String validJavaDoc = getJavaDocPath(AdtPlugin.getOsAbsoluteFramework());
// now compare and return
return validJavaDoc.equals(att.getValue());
}
}
return false;
}
/**
* Fix the project. This checks the SDK location.
* @param project The project to fix.
@@ -264,14 +238,7 @@ public final class ProjectHelper {
continue;
}
} else if (kind == IClasspathEntry.CPE_CONTAINER) {
IPath containerPath = entry.getPath();
if (AndroidClasspathContainerInitializer.checkOldPath(containerPath)) {
entries = ProjectHelper.removeEntryFromClasspath(entries, i);
// continue, to skip the i++;
continue;
} else if (AndroidClasspathContainerInitializer.checkPath(containerPath)) {
if (AndroidClasspathContainerInitializer.checkPath(entry.getPath())) {
foundContainer = true;
}
}

View File

@@ -18,7 +18,10 @@ package com.android.ide.eclipse.adt.project.export;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.jarutils.KeystoreHelper;
import com.android.jarutils.SignedJarBuilder;
import com.android.jarutils.DebugKeyProvider.IKeyGenOutput;
import com.android.jarutils.DebugKeyProvider.KeytoolException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
@@ -27,28 +30,38 @@ import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IExportWizard;
import org.eclipse.ui.IWorkbench;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
/**
* Export wizard to export an apk signed with a release key/certificate.
*/
public class ExportWizard extends Wizard implements IExportWizard {
public final class ExportWizard extends Wizard implements IExportWizard {
private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$
private static final String PAGE_PRE = "preExportPage"; //$NON-NLS-1$
private static final String PAGE_SIGNING = "signingExportPage"; //$NON-NLS-1$
private static final String PAGE_FINAL = "finalExportPage"; //$NON-NLS-1$
private static final String PAGE_PROJECT_CHECK = "Page_ProjectCheck"; //$NON-NLS-1$
private static final String PAGE_KEYSTORE_SELECTION = "Page_KeystoreSelection"; //$NON-NLS-1$
private static final String PAGE_KEY_CREATION = "Page_KeyCreation"; //$NON-NLS-1$
private static final String PAGE_KEY_SELECTION = "Page_KeySelection"; //$NON-NLS-1$
private static final String PAGE_KEY_CHECK = "Page_KeyCheck"; //$NON-NLS-1$
static final String PROPERTY_KEYSTORE = "keystore"; //$NON-NLS-1$
static final String PROPERTY_ALIAS = "alias"; //$NON-NLS-1$
@@ -59,7 +72,41 @@ public class ExportWizard extends Wizard implements IExportWizard {
*/
static abstract class ExportWizardPage extends WizardPage {
protected boolean mNewProjectReference = true;
/** bit mask constant for project data change event */
protected static final int DATA_PROJECT = 0x001;
/** bit mask constant for keystore data change event */
protected static final int DATA_KEYSTORE = 0x002;
/** bit mask constant for key data change event */
protected static final int DATA_KEY = 0x004;
protected static final VerifyListener sPasswordVerifier = new VerifyListener() {
public void verifyText(VerifyEvent e) {
// verify the characters are valid for password.
int len = e.text.length();
// first limit to 127 characters max
if (len + ((Text)e.getSource()).getText().length() > 127) {
e.doit = false;
return;
}
// now only take non control characters
for (int i = 0 ; i < len ; i++) {
if (e.text.charAt(i) < 32) {
e.doit = false;
return;
}
}
}
};
/**
* Bit mask indicating what changed while the page was hidden.
* @see #DATA_PROJECT
* @see #DATA_KEYSTORE
* @see #DATA_KEY
*/
protected int mProjectDataChanged = 0;
ExportWizardPage(String name) {
super(name);
@@ -72,23 +119,38 @@ public class ExportWizard extends Wizard implements IExportWizard {
super.setVisible(visible);
if (visible) {
onShow();
mNewProjectReference = false;
mProjectDataChanged = 0;
}
}
void newProjectReference() {
mNewProjectReference = true;
final void projectDataChanged(int changeMask) {
mProjectDataChanged |= changeMask;
}
/**
* Calls {@link #setErrorMessage(String)} and {@link #setPageComplete(boolean)} based on a
* {@link Throwable} object.
*/
protected final void onException(Throwable t) {
String message = getExceptionMessage(t);
setErrorMessage(message);
setPageComplete(false);
}
}
private ExportWizardPage mPages[] = new ExportWizardPage[3];
private ExportWizardPage mPages[] = new ExportWizardPage[5];
private IProject mProject;
private String mKeystore;
private String mKeystorePassword;
private boolean mKeystoreCreationMode;
private String mKeyAlias;
private char[] mKeystorePassword;
private char[] mKeyPassword;
private String mKeyPassword;
private int mValidity;
private String mDName;
private PrivateKey mPrivateKey;
private X509Certificate mCertificate;
@@ -97,6 +159,15 @@ public class ExportWizard extends Wizard implements IExportWizard {
private String mApkFilePath;
private String mApkFileName;
private ExportWizardPage mKeystoreSelectionPage;
private ExportWizardPage mKeyCreationPage;
private ExportWizardPage mKeySelectionPage;
private ExportWizardPage mKeyCheckPage;
private boolean mKeyCreationMode;
private List<String> mExistingAliases;
public ExportWizard() {
setHelpAvailable(false); // TODO have help
setWindowTitle("Export Android Application");
@@ -105,46 +176,101 @@ public class ExportWizard extends Wizard implements IExportWizard {
@Override
public void addPages() {
addPage(mPages[0] = new PreExportPage(this, PAGE_PRE));
addPage(mPages[1] = new SigningExportPage(this, PAGE_SIGNING));
addPage(mPages[2] = new FinalExportPage(this, PAGE_FINAL));
addPage(mPages[0] = new ProjectCheckPage(this, PAGE_PROJECT_CHECK));
addPage(mKeystoreSelectionPage = mPages[1] = new KeystoreSelectionPage(this,
PAGE_KEYSTORE_SELECTION));
addPage(mKeyCreationPage = mPages[2] = new KeyCreationPage(this, PAGE_KEY_CREATION));
addPage(mKeySelectionPage = mPages[3] = new KeySelectionPage(this, PAGE_KEY_SELECTION));
addPage(mKeyCheckPage = mPages[4] = new KeyCheckPage(this, PAGE_KEY_CHECK));
}
@Override
public boolean performFinish() {
// first we make sure export is fine if the destination file already exists
File f = new File(mDestinationPath);
if (f.isFile()) {
if (AdtPlugin.displayPrompt("Export Wizard",
"File already exists. Do you want to overwrite it?") == false) {
return false;
}
}
// save the properties
ProjectHelper.saveStringProperty(mProject, PROPERTY_KEYSTORE, mKeystore);
ProjectHelper.saveStringProperty(mProject, PROPERTY_ALIAS, mKeyAlias);
ProjectHelper.saveStringProperty(mProject, PROPERTY_DESTINATION, mDestinationPath);
try {
FileOutputStream fos = new FileOutputStream(mDestinationPath);
SignedJarBuilder builder = new SignedJarBuilder(fos, mPrivateKey, mCertificate);
// get the input file.
FileInputStream fis = new FileInputStream(mApkFilePath);
try {
builder.writeZip(fis, null /* filter */);
} finally {
if (mKeystoreCreationMode || mKeyCreationMode) {
final ArrayList<String> output = new ArrayList<String>();
if (KeystoreHelper.createNewStore(
mKeystore,
null /*storeType*/,
mKeystorePassword,
mKeyAlias,
mKeyPassword,
mDName,
mValidity,
new IKeyGenOutput() {
public void err(String message) {
output.add(message);
}
public void out(String message) {
output.add(message);
}
}) == false) {
// keystore creation error!
displayError(output.toArray(new String[output.size()]));
return false;
}
// keystore is created, now load the private key and certificate.
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream fis = new FileInputStream(mKeystore);
keyStore.load(fis, mKeystorePassword.toCharArray());
fis.close();
PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
mKeyAlias, new KeyStore.PasswordProtection(mKeyPassword.toCharArray()));
if (entry != null) {
mPrivateKey = entry.getPrivateKey();
mCertificate = (X509Certificate)entry.getCertificate();
} else {
// this really shouldn't happen since we now let the user choose the key
// from a list read from the store.
displayError("Could not find key");
return false;
}
}
builder.close();
fos.close();
return true;
// check the private key/certificate again since it may have been created just above.
if (mPrivateKey != null && mCertificate != null) {
FileOutputStream fos = new FileOutputStream(mDestinationPath);
SignedJarBuilder builder = new SignedJarBuilder(fos, mPrivateKey, mCertificate);
// get the input file.
FileInputStream fis = new FileInputStream(mApkFilePath);
try {
builder.writeZip(fis, null /* filter */);
} finally {
fis.close();
}
builder.close();
fos.close();
return true;
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
displayError(e);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
displayError(e);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
displayError(e);
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
displayError(e);
} catch (KeytoolException e) {
displayError(e);
}
return false;
@@ -152,8 +278,13 @@ public class ExportWizard extends Wizard implements IExportWizard {
@Override
public boolean canFinish() {
// check if we have the apk to resign, the destination location, and either
// a private key/certificate or the creation mode. In creation mode, unless
// all the key/keystore info is valid, the user cannot reach the last page, so there's
// no need to check them again here.
return mApkFilePath != null &&
mPrivateKey != null && mCertificate != null &&
((mPrivateKey != null && mCertificate != null)
|| mKeystoreCreationMode || mKeyCreationMode) &&
mDestinationPath != null;
}
@@ -175,6 +306,22 @@ public class ExportWizard extends Wizard implements IExportWizard {
}
}
ExportWizardPage getKeystoreSelectionPage() {
return mKeystoreSelectionPage;
}
ExportWizardPage getKeyCreationPage() {
return mKeyCreationPage;
}
ExportWizardPage getKeySelectionPage() {
return mKeySelectionPage;
}
ExportWizardPage getKeyCheckPage() {
return mKeyCheckPage;
}
/**
* Returns an image descriptor for the wizard logo.
*/
@@ -192,10 +339,7 @@ public class ExportWizard extends Wizard implements IExportWizard {
mApkFilePath = apkFilePath;
mApkFileName = filename;
// indicate to the page that the project was changed.
for (ExportWizardPage page : mPages) {
page.newProjectReference();
}
updatePageOnChange(ExportWizardPage.DATA_PROJECT);
}
String getApkFilename() {
@@ -206,42 +350,95 @@ public class ExportWizard extends Wizard implements IExportWizard {
mKeystore = path;
mPrivateKey = null;
mCertificate = null;
updatePageOnChange(ExportWizardPage.DATA_KEYSTORE);
}
String getKeystore() {
return mKeystore;
}
void setKeystoreCreationMode(boolean createStore) {
mKeystoreCreationMode = createStore;
updatePageOnChange(ExportWizardPage.DATA_KEYSTORE);
}
boolean getKeystoreCreationMode() {
return mKeystoreCreationMode;
}
void setKeystorePassword(String password) {
mKeystorePassword = password;
mPrivateKey = null;
mCertificate = null;
updatePageOnChange(ExportWizardPage.DATA_KEYSTORE);
}
String getKeystorePassword() {
return mKeystorePassword;
}
void setKeyCreationMode(boolean createKey) {
mKeyCreationMode = createKey;
updatePageOnChange(ExportWizardPage.DATA_KEY);
}
boolean getKeyCreationMode() {
return mKeyCreationMode;
}
void setExistingAliases(List<String> aliases) {
mExistingAliases = aliases;
}
List<String> getExistingAliases() {
return mExistingAliases;
}
void setKeyAlias(String name) {
mKeyAlias = name;
mPrivateKey = null;
mCertificate = null;
updatePageOnChange(ExportWizardPage.DATA_KEY);
}
String getKeyAlias() {
return mKeyAlias;
}
void setKeystorePassword(char[] password) {
mKeystorePassword = password;
mPrivateKey = null;
mCertificate = null;
}
char[] getKeystorePassword() {
return mKeystorePassword;
}
void setKeyPassword(char[] password) {
void setKeyPassword(String password) {
mKeyPassword = password;
mPrivateKey = null;
mCertificate = null;
updatePageOnChange(ExportWizardPage.DATA_KEY);
}
char[] getKeyPassword() {
String getKeyPassword() {
return mKeyPassword;
}
void setValidity(int validity) {
mValidity = validity;
updatePageOnChange(ExportWizardPage.DATA_KEY);
}
int getValidity() {
return mValidity;
}
void setDName(String dName) {
mDName = dName;
updatePageOnChange(ExportWizardPage.DATA_KEY);
}
String getDName() {
return mDName;
}
void setSigningInfo(PrivateKey privateKey, X509Certificate certificate) {
mPrivateKey = privateKey;
mCertificate = certificate;
@@ -251,4 +448,54 @@ public class ExportWizard extends Wizard implements IExportWizard {
mDestinationPath = path;
}
void updatePageOnChange(int changeMask) {
for (ExportWizardPage page : mPages) {
page.projectDataChanged(changeMask);
}
}
private void displayError(String... messages) {
String message = null;
if (messages.length == 1) {
message = messages[0];
} else {
StringBuilder sb = new StringBuilder(messages[0]);
for (int i = 1; i < messages.length; i++) {
sb.append('\n');
sb.append(messages[i]);
}
message = sb.toString();
}
AdtPlugin.displayError("Export Wizard", message);
}
private void displayError(Exception e) {
String message = getExceptionMessage(e);
displayError(message);
AdtPlugin.log(e, "Export Wizard Error");
}
/**
* Returns the {@link Throwable#getMessage()}. If the {@link Throwable#getMessage()} returns
* <code>null</code>, the method is called again on the cause of the Throwable object.
* <p/>If no Throwable in the chain has a valid message, the canonical name of the first
* exception is returned.
*/
private static String getExceptionMessage(Throwable t) {
String message = t.getMessage();
if (message == null) {
Throwable cause = t.getCause();
if (cause != null) {
return getExceptionMessage(cause);
}
// no more cause and still no message. display the first exception.
return cause.getClass().getCanonicalName();
}
return message;
}
}

View File

@@ -1,275 +0,0 @@
/*
* 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.project.export;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
import org.eclipse.core.resources.IProject;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.widgets.FormText;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Calendar;
public class FinalExportPage extends ExportWizardPage {
private final ExportWizard mWizard;
private PrivateKey mPrivateKey;
private X509Certificate mCertificate;
private Text mDestination;
private Button mBrowseButton;
private boolean mFatalSigningError;
private FormText mDetailText;
protected FinalExportPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Application Export");
setDescription("Export the signed Application package.");
}
public void createControl(Composite parent) {
setErrorMessage(null);
setMessage(null);
// build the ui.
Composite composite = new Composite(parent, SWT.NULL);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
GridLayout gl = new GridLayout(3, false);
gl.verticalSpacing *= 3;
composite.setLayout(gl);
GridData gd;
new Label(composite, SWT.NONE).setText("Destination APK file:");
mDestination = new Text(composite, SWT.BORDER);
mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mDestination.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
onDestinationChange();
}
});
mBrowseButton = new Button(composite, SWT.PUSH);
mBrowseButton.setText("Browse...");
mBrowseButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
FileDialog fileDialog = new FileDialog(mBrowseButton.getShell(), SWT.SAVE);
fileDialog.setText("Destination file name");
fileDialog.setFileName(mWizard.getApkFilename());
String saveLocation = fileDialog.open();
if (saveLocation != null) {
mDestination.setText(saveLocation);
}
}
});
mDetailText = new FormText(composite, SWT.NONE);
mDetailText.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
gd.horizontalSpan = 3;
setControl(composite);
}
@Override
void onShow() {
// fill the texts with information loaded from the project.
if (mNewProjectReference) {
// reset the destination from the content of the project
IProject project = mWizard.getProject();
String destination = ProjectHelper.loadStringProperty(project,
ExportWizard.PROPERTY_DESTINATION);
if (destination != null) {
mDestination.setText(destination);
}
}
// reset the wizard with no key/cert to make it not finishable, unless a valid
// key/cert is found.
mWizard.setSigningInfo(null, null);
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream fis = new FileInputStream(mWizard.getKeystore());
keyStore.load(fis, mWizard.getKeystorePassword());
fis.close();
PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
mWizard.getKeyAlias(),
new KeyStore.PasswordProtection(mWizard.getKeyPassword()));
if (entry != null) {
mPrivateKey = entry.getPrivateKey();
mCertificate = (X509Certificate)entry.getCertificate();
} else {
setErrorMessage("Unable to find key");
setPageComplete(false);
}
} catch (FileNotFoundException e) {
// this was checked at the first previous step and will not happen here, unless
// the file was removed during the export wizard execution.
onException(e);
} catch (KeyStoreException e) {
onException(e);
} catch (NoSuchAlgorithmException e) {
onException(e);
} catch (UnrecoverableEntryException e) {
onException(e);
} catch (CertificateException e) {
onException(e);
} catch (IOException e) {
onException(e);
}
if (mPrivateKey != null && mCertificate != null) {
mFatalSigningError = false;
Calendar expirationCalendar = Calendar.getInstance();
expirationCalendar.setTime(mCertificate.getNotAfter());
Calendar today = Calendar.getInstance();
if (expirationCalendar.before(today)) {
mDetailText.setText(String.format("<form><p>Certificate expired on %s</p></form>",
mCertificate.getNotAfter().toString()),
true /* parseTags */, true /* expandURLs */);
// fatal error = nothing can make the page complete.
mFatalSigningError = true;
setErrorMessage("Certificate is expired!");
setPageComplete(false);
} else {
// valid, key/cert: put it in the wizard so that it can be finished
mWizard.setSigningInfo(mPrivateKey, mCertificate);
StringBuilder sb = new StringBuilder(String.format("<form><p>Certificate expires on %s.</p>",
mCertificate.getNotAfter().toString()));
int expirationYear = expirationCalendar.get(Calendar.YEAR);
int thisYear = today.get(Calendar.YEAR);
if (thisYear + 25 < expirationYear) {
// do nothing
} else {
if (expirationYear == thisYear) {
sb.append("<p>The certificate expires this year!</p>");
} else {
int count = expirationYear-thisYear;
sb.append(String.format("<p>The Certificate expires in %1$s %2$s!</p>",
count, count == 1 ? "year" : "years"));
}
sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
sb.append("</form>");
}
mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */);
}
mDetailText.getParent().layout();
} else {
// fatal error = nothing can make the page complete.
mFatalSigningError = true;
}
onDestinationChange();
}
private void onDestinationChange() {
if (mFatalSigningError == false) {
String path = mDestination.getText().trim();
if (path.length() == 0) {
setErrorMessage("Enter destination for the APK file.");
mWizard.setDestination(null); // this is to reset canFinish in the wizard
setPageComplete(false);
} else {
File file = new File(path);
if (file.isDirectory()) {
setErrorMessage("Destination is a directory!");
mWizard.setDestination(null); // this is to reset canFinish in the wizard
setPageComplete(false);
} else {
File parentFile = file.getParentFile();
if (parentFile == null || parentFile.isDirectory() == false) {
setErrorMessage("Not a valid directory.");
mWizard.setDestination(null); // this is to reset canFinish in the wizard
setPageComplete(false);
} else {
mWizard.setDestination(path);
setErrorMessage(null);
setPageComplete(true);
}
}
}
}
}
/**
* Calls {@link #setErrorMessage(String)} and {@link #setPageComplete(boolean)} based on a
* {@link Throwable} object. If the {@link Throwable#getMessage()} returns <code>null</code>,
* the method is called again on the cause of the Throwable.
*/
private void onException(Throwable t) {
String message = t.getMessage();
if (message == null) {
Throwable cause = t.getCause();
if (cause != null) {
onException(cause);
} else {
// no more cause and still no message. display the first exception.
setErrorMessage(cause.getClass().getCanonicalName());
setPageComplete(false);
}
return;
}
setErrorMessage(message);
setPageComplete(false);
}
}

View File

@@ -0,0 +1,291 @@
/*
* 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.project.export;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
import org.eclipse.core.resources.IProject;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.widgets.FormText;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Calendar;
/**
* Final page of the wizard that checks the key and ask for the ouput location.
*/
final class KeyCheckPage extends ExportWizardPage {
private final ExportWizard mWizard;
private PrivateKey mPrivateKey;
private X509Certificate mCertificate;
private Text mDestination;
private boolean mFatalSigningError;
private FormText mDetailText;
protected KeyCheckPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Destination and key/certificate checks");
setDescription(""); // TODO
}
public void createControl(Composite parent) {
setErrorMessage(null);
setMessage(null);
// build the ui.
Composite composite = new Composite(parent, SWT.NULL);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
GridLayout gl = new GridLayout(3, false);
gl.verticalSpacing *= 3;
composite.setLayout(gl);
GridData gd;
new Label(composite, SWT.NONE).setText("Destination APK file:");
mDestination = new Text(composite, SWT.BORDER);
mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mDestination.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
onDestinationChange();
}
});
final Button browseButton = new Button(composite, SWT.PUSH);
browseButton.setText("Browse...");
browseButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.SAVE);
fileDialog.setText("Destination file name");
fileDialog.setFileName(mWizard.getApkFilename());
String saveLocation = fileDialog.open();
if (saveLocation != null) {
mDestination.setText(saveLocation);
}
}
});
mDetailText = new FormText(composite, SWT.NONE);
mDetailText.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
gd.horizontalSpan = 3;
setControl(composite);
}
@Override
void onShow() {
// fill the texts with information loaded from the project.
if ((mProjectDataChanged & DATA_PROJECT) != 0) {
// reset the destination from the content of the project
IProject project = mWizard.getProject();
String destination = ProjectHelper.loadStringProperty(project,
ExportWizard.PROPERTY_DESTINATION);
if (destination != null) {
mDestination.setText(destination);
}
}
// if anything change we basically reload the data.
if (mProjectDataChanged != 0) {
mFatalSigningError = false;
// reset the wizard with no key/cert to make it not finishable, unless a valid
// key/cert is found.
mWizard.setSigningInfo(null, null);
if (mWizard.getKeystoreCreationMode() || mWizard.getKeyCreationMode()) {
int validity = mWizard.getValidity();
StringBuilder sb = new StringBuilder(
String.format("<form><p>Certificate expires in %d years.</p>",
validity));
if (validity < 25) {
sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
}
sb.append("</form>");
mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */);
} else {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream fis = new FileInputStream(mWizard.getKeystore());
keyStore.load(fis, mWizard.getKeystorePassword().toCharArray());
fis.close();
PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
mWizard.getKeyAlias(),
new KeyStore.PasswordProtection(
mWizard.getKeyPassword().toCharArray()));
if (entry != null) {
mPrivateKey = entry.getPrivateKey();
mCertificate = (X509Certificate)entry.getCertificate();
} else {
setErrorMessage("Unable to find key.");
setPageComplete(false);
}
} catch (FileNotFoundException e) {
// this was checked at the first previous step and will not happen here, unless
// the file was removed during the export wizard execution.
onException(e);
} catch (KeyStoreException e) {
onException(e);
} catch (NoSuchAlgorithmException e) {
onException(e);
} catch (UnrecoverableEntryException e) {
onException(e);
} catch (CertificateException e) {
onException(e);
} catch (IOException e) {
onException(e);
}
if (mPrivateKey != null && mCertificate != null) {
Calendar expirationCalendar = Calendar.getInstance();
expirationCalendar.setTime(mCertificate.getNotAfter());
Calendar today = Calendar.getInstance();
if (expirationCalendar.before(today)) {
mDetailText.setText(String.format(
"<form><p>Certificate expired on %s</p></form>",
mCertificate.getNotAfter().toString()),
true /* parseTags */, true /* expandURLs */);
// fatal error = nothing can make the page complete.
mFatalSigningError = true;
setErrorMessage("Certificate is expired.");
setPageComplete(false);
} else {
// valid, key/cert: put it in the wizard so that it can be finished
mWizard.setSigningInfo(mPrivateKey, mCertificate);
StringBuilder sb = new StringBuilder(String.format(
"<form><p>Certificate expires on %s.</p>",
mCertificate.getNotAfter().toString()));
int expirationYear = expirationCalendar.get(Calendar.YEAR);
int thisYear = today.get(Calendar.YEAR);
if (thisYear + 25 < expirationYear) {
// do nothing
} else {
if (expirationYear == thisYear) {
sb.append("<p>The certificate expires this year.</p>");
} else {
int count = expirationYear-thisYear;
sb.append(String.format(
"<p>The Certificate expires in %1$s %2$s.</p>",
count, count == 1 ? "year" : "years"));
}
sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
}
sb.append("</form>");
mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */);
}
mDetailText.getParent().layout();
} else {
// fatal error = nothing can make the page complete.
mFatalSigningError = true;
}
}
}
onDestinationChange();
}
private void onDestinationChange() {
if (mFatalSigningError == false) {
// reset messages for now.
setErrorMessage(null);
setMessage(null);
String path = mDestination.getText().trim();
if (path.length() == 0) {
setErrorMessage("Enter destination for the APK file.");
mWizard.setDestination(null); // this is to reset canFinish in the wizard
setPageComplete(false);
return;
}
File file = new File(path);
if (file.isDirectory()) {
setErrorMessage("Destination is a directory.");
mWizard.setDestination(null); // this is to reset canFinish in the wizard
setPageComplete(false);
return;
}
File parentFile = file.getParentFile();
if (parentFile == null || parentFile.isDirectory() == false) {
setErrorMessage("Not a valid directory.");
mWizard.setDestination(null); // this is to reset canFinish in the wizard
setPageComplete(false);
return;
}
// no error, set the destination in the wizard.
mWizard.setDestination(path);
setPageComplete(true);
// However, we should also test if the file already exists.
if (file.isFile()) {
setMessage("Destination file already exists.", WARNING);
}
}
}
}

View File

@@ -0,0 +1,332 @@
/*
* 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.project.export;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import java.util.List;
/**
* Key creation page.
*/
final class KeyCreationPage extends ExportWizardPage {
private final ExportWizard mWizard;
private Text mAlias;
private Text mKeyPassword;
private Text mKeyPassword2;
private Text mCnField;
private boolean mDisableOnChange = false;
private Text mOuField;
private Text mOField;
private Text mLField;
private Text mStField;
private Text mCField;
private String mDName;
private int mValidity = 0;
private List<String> mExistingAliases;
protected KeyCreationPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Key Creation");
setDescription(""); // TODO?
}
public void createControl(Composite parent) {
Composite composite = new Composite(parent, SWT.NULL);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
GridLayout gl = new GridLayout(2, false);
composite.setLayout(gl);
GridData gd;
new Label(composite, SWT.NONE).setText("Alias:");
mAlias = new Text(composite, SWT.BORDER);
mAlias.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.NONE).setText("Password:");
mKeyPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeyPassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mKeyPassword.addVerifyListener(sPasswordVerifier);
new Label(composite, SWT.NONE).setText("Confirm:");
mKeyPassword2 = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeyPassword2.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mKeyPassword2.addVerifyListener(sPasswordVerifier);
new Label(composite, SWT.NONE).setText("Validity (years):");
final Text validityText = new Text(composite, SWT.BORDER);
validityText.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
validityText.addVerifyListener(new VerifyListener() {
public void verifyText(VerifyEvent e) {
// check for digit only.
for (int i = 0 ; i < e.text.length(); i++) {
char letter = e.text.charAt(i);
if (letter < '0' || letter > '9') {
e.doit = false;
return;
}
}
}
});
new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(
gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 2;
new Label(composite, SWT.NONE).setText("First and Last Name:");
mCnField = new Text(composite, SWT.BORDER);
mCnField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.NONE).setText("Organizational Unit:");
mOuField = new Text(composite, SWT.BORDER);
mOuField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.NONE).setText("Organization:");
mOField = new Text(composite, SWT.BORDER);
mOField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.NONE).setText("City or Locality:");
mLField = new Text(composite, SWT.BORDER);
mLField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.NONE).setText("State or Province:");
mStField = new Text(composite, SWT.BORDER);
mStField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.NONE).setText("Country Code (XX):");
mCField = new Text(composite, SWT.BORDER);
mCField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
// Show description the first time
setErrorMessage(null);
setMessage(null);
setControl(composite);
mAlias.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeyAlias(mAlias.getText().trim());
onChange();
}
});
mKeyPassword.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeyPassword(mKeyPassword.getText());
onChange();
}
});
mKeyPassword2.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
onChange();
}
});
validityText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
try {
mValidity = Integer.parseInt(validityText.getText());
} catch (NumberFormatException e2) {
// this should only happen if the text field is empty due to the verifyListener.
mValidity = 0;
}
mWizard.setValidity(mValidity);
onChange();
}
});
ModifyListener dNameListener = new ModifyListener() {
public void modifyText(ModifyEvent e) {
onDNameChange();
}
};
mCnField.addModifyListener(dNameListener);
mOuField.addModifyListener(dNameListener);
mOField.addModifyListener(dNameListener);
mLField.addModifyListener(dNameListener);
mStField.addModifyListener(dNameListener);
mCField.addModifyListener(dNameListener);
}
@Override
void onShow() {
// fill the texts with information loaded from the project.
if ((mProjectDataChanged & (DATA_PROJECT | DATA_KEYSTORE)) != 0) {
// reset the keystore/alias from the content of the project
IProject project = mWizard.getProject();
// disable onChange for now. we'll call it once at the end.
mDisableOnChange = true;
String alias = ProjectHelper.loadStringProperty(project, ExportWizard.PROPERTY_ALIAS);
if (alias != null) {
mAlias.setText(alias);
}
// get the existing list of keys if applicable
if (mWizard.getKeyCreationMode()) {
mExistingAliases = mWizard.getExistingAliases();
} else {
mExistingAliases = null;
}
// reset the passwords
mKeyPassword.setText(""); //$NON-NLS-1$
mKeyPassword2.setText(""); //$NON-NLS-1$
// enable onChange, and call it to display errors and enable/disable pageCompleted.
mDisableOnChange = false;
onChange();
}
}
@Override
public IWizardPage getPreviousPage() {
if (mWizard.getKeyCreationMode()) { // this means we create a key from an existing store
return mWizard.getKeySelectionPage();
}
return mWizard.getKeystoreSelectionPage();
}
@Override
public IWizardPage getNextPage() {
return mWizard.getKeyCheckPage();
}
/**
* Handles changes and update the error message and calls {@link #setPageComplete(boolean)}.
*/
private void onChange() {
if (mDisableOnChange) {
return;
}
setErrorMessage(null);
setMessage(null);
if (mAlias.getText().trim().length() == 0) {
setErrorMessage("Enter key alias.");
setPageComplete(false);
return;
} else if (mExistingAliases != null) {
// we cannot use indexOf, because we need to do a case-insensitive check
String keyAlias = mAlias.getText().trim();
for (String alias : mExistingAliases) {
if (alias.equalsIgnoreCase(keyAlias)) {
setErrorMessage("Key alias already exists in keystore.");
setPageComplete(false);
return;
}
}
}
String value = mKeyPassword.getText();
if (value.length() == 0) {
setErrorMessage("Enter key password.");
setPageComplete(false);
return;
} else if (value.length() < 6) {
setErrorMessage("Key password is too short - must be at least 6 characters.");
setPageComplete(false);
return;
}
if (value.equals(mKeyPassword2.getText()) == false) {
setErrorMessage("Key passwords don't match.");
setPageComplete(false);
return;
}
if (mValidity == 0) {
setErrorMessage("Key certificate validity is required.");
setPageComplete(false);
return;
} else if (mValidity < 25) {
setMessage("A 25 year certificate validity is recommended.", WARNING);
} else if (mValidity > 1000) {
setErrorMessage("Key certificate validity must be between 1 and 1000 years.");
setPageComplete(false);
return;
}
if (mDName == null || mDName.length() == 0) {
setErrorMessage("At least one Certificate issuer field is required to be non-empty.");
setPageComplete(false);
return;
}
setPageComplete(true);
}
/**
* Handles changes in the DName fields.
*/
private void onDNameChange() {
StringBuilder sb = new StringBuilder();
buildDName("CN", mCnField, sb);
buildDName("OU", mOuField, sb);
buildDName("O", mOField, sb);
buildDName("L", mLField, sb);
buildDName("ST", mStField, sb);
buildDName("C", mCField, sb);
mDName = sb.toString();
mWizard.setDName(mDName);
onChange();
}
/**
* Builds the distinguished name string with the provided {@link StringBuilder}.
* @param prefix the prefix of the entry.
* @param textField The {@link Text} field containing the entry value.
* @param sb the string builder containing the dname.
*/
private void buildDName(String prefix, Text textField, StringBuilder sb) {
if (textField != null) {
String value = textField.getText().trim();
if (value.length() > 0) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(prefix);
sb.append('=');
sb.append(value);
}
}
}
}

View File

@@ -0,0 +1,266 @@
/*
* 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.project.export;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Enumeration;
/**
* Key Selection Page. This is used when an existing keystore is used.
*/
final class KeySelectionPage extends ExportWizardPage {
private final ExportWizard mWizard;
private Label mKeyAliasesLabel;
private Combo mKeyAliases;
private Label mKeyPasswordLabel;
private Text mKeyPassword;
private boolean mDisableOnChange = false;
private Button mUseExistingKey;
private Button mCreateKey;
protected KeySelectionPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Key alias selection");
setDescription(""); // TODO
}
public void createControl(Composite parent) {
Composite composite = new Composite(parent, SWT.NULL);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
GridLayout gl = new GridLayout(3, false);
composite.setLayout(gl);
GridData gd;
mUseExistingKey = new Button(composite, SWT.RADIO);
mUseExistingKey.setText("Use existing key");
mUseExistingKey.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
mUseExistingKey.setSelection(true);
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
gd.heightHint = 0;
gd.widthHint = 50;
mKeyAliasesLabel = new Label(composite, SWT.NONE);
mKeyAliasesLabel.setText("Alias:");
mKeyAliases = new Combo(composite, SWT.READ_ONLY);
mKeyAliases.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
gd.heightHint = 0;
gd.widthHint = 50;
mKeyPasswordLabel = new Label(composite, SWT.NONE);
mKeyPasswordLabel.setText("Password:");
mKeyPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeyPassword.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mCreateKey = new Button(composite, SWT.RADIO);
mCreateKey.setText("Create new key");
mCreateKey.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
// Show description the first time
setErrorMessage(null);
setMessage(null);
setControl(composite);
mUseExistingKey.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
mWizard.setKeyCreationMode(!mUseExistingKey.getSelection());
enableWidgets();
onChange();
}
});
mKeyAliases.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
mWizard.setKeyAlias(mKeyAliases.getItem(mKeyAliases.getSelectionIndex()));
onChange();
}
});
mKeyPassword.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeyPassword(mKeyPassword.getText());
onChange();
}
});
}
@Override
void onShow() {
// fill the texts with information loaded from the project.
if ((mProjectDataChanged & (DATA_PROJECT | DATA_KEYSTORE)) != 0) {
// disable onChange for now. we'll call it once at the end.
mDisableOnChange = true;
// reset the alias from the content of the project
try {
// reset to using a key
mWizard.setKeyCreationMode(false);
mUseExistingKey.setSelection(true);
mCreateKey.setSelection(false);
enableWidgets();
// remove the content of the alias combo always and first, in case the
// keystore password is wrong
mKeyAliases.removeAll();
// get the alias list (also used as a keystore password test)
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream fis = new FileInputStream(mWizard.getKeystore());
keyStore.load(fis, mWizard.getKeystorePassword().toCharArray());
fis.close();
Enumeration<String> aliases = keyStore.aliases();
// get the alias from the project previous export, and look for a match as
// we add the aliases to the combo.
IProject project = mWizard.getProject();
String keyAlias = ProjectHelper.loadStringProperty(project,
ExportWizard.PROPERTY_ALIAS);
ArrayList<String> aliasList = new ArrayList<String>();
int selection = -1;
int count = 0;
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
mKeyAliases.add(alias);
aliasList.add(alias);
if (selection == -1 && alias.equalsIgnoreCase(keyAlias)) {
selection = count;
}
count++;
}
mWizard.setExistingAliases(aliasList);
if (selection != -1) {
mKeyAliases.select(selection);
// since a match was found and is selected, we need to give it to
// the wizard as well
mWizard.setKeyAlias(keyAlias);
} else {
mKeyAliases.clearSelection();
}
// reset the password
mKeyPassword.setText(""); //$NON-NLS-1$
// enable onChange, and call it to display errors and enable/disable pageCompleted.
mDisableOnChange = false;
onChange();
} catch (KeyStoreException e) {
onException(e);
} catch (FileNotFoundException e) {
onException(e);
} catch (NoSuchAlgorithmException e) {
onException(e);
} catch (CertificateException e) {
onException(e);
} catch (IOException e) {
onException(e);
} finally {
// in case we exit with an exception, we need to reset this
mDisableOnChange = false;
}
}
}
@Override
public IWizardPage getPreviousPage() {
return mWizard.getKeystoreSelectionPage();
}
@Override
public IWizardPage getNextPage() {
if (mWizard.getKeyCreationMode()) {
return mWizard.getKeyCreationPage();
}
return mWizard.getKeyCheckPage();
}
/**
* Handles changes and update the error message and calls {@link #setPageComplete(boolean)}.
*/
private void onChange() {
if (mDisableOnChange) {
return;
}
setErrorMessage(null);
setMessage(null);
if (mWizard.getKeyCreationMode() == false) {
if (mKeyAliases.getSelectionIndex() == -1) {
setErrorMessage("Select a key alias.");
setPageComplete(false);
return;
}
if (mKeyPassword.getText().trim().length() == 0) {
setErrorMessage("Enter key password.");
setPageComplete(false);
return;
}
}
setPageComplete(true);
}
private void enableWidgets() {
boolean useKey = !mWizard.getKeyCreationMode();
mKeyAliasesLabel.setEnabled(useKey);
mKeyAliases.setEnabled(useKey);
mKeyPassword.setEnabled(useKey);
mKeyPasswordLabel.setEnabled(useKey);
}
}

View File

@@ -20,6 +20,7 @@ import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
@@ -36,23 +37,26 @@ import org.eclipse.swt.widgets.Text;
import java.io.File;
/**
* Second export wizard page.
* Keystore selection page. This page allows to choose to create a new keystore or use an
* existing one.
*/
public class SigningExportPage extends ExportWizardPage {
final class KeystoreSelectionPage extends ExportWizardPage {
private final ExportWizard mWizard;
private Button mUseExistingKeystore;
private Button mCreateKeystore;
private Text mKeystore;
private Text mAlias;
private Text mKeystorePassword;
private Text mKeyPassword;
private Label mConfirmLabel;
private Text mKeystorePassword2;
private boolean mDisableOnChange = false;
protected SigningExportPage(ExportWizard wizard, String pageName) {
protected KeystoreSelectionPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Application Signing");
setDescription("Defines which store, key and certificate to use to sign the Android Application.");
setTitle("Keystore selection");
setDescription(""); //TODO
}
public void createControl(Composite parent) {
@@ -62,8 +66,19 @@ public class SigningExportPage extends ExportWizardPage {
composite.setLayout(gl);
GridData gd;
mUseExistingKeystore = new Button(composite, SWT.RADIO);
mUseExistingKeystore.setText("Use existing keystore");
mUseExistingKeystore.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
mUseExistingKeystore.setSelection(true);
new Label(composite, SWT.NONE).setText("Keystore:");
mCreateKeystore = new Button(composite, SWT.RADIO);
mCreateKeystore.setText("Create new keystore");
mCreateKeystore.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
new Label(composite, SWT.NONE).setText("Location:");
mKeystore = new Text(composite, SWT.BORDER);
mKeystore.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
final Button browseButton = new Button(composite, SWT.PUSH);
@@ -71,8 +86,14 @@ public class SigningExportPage extends ExportWizardPage {
browseButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.OPEN);
fileDialog.setText("Load Keystore");
FileDialog fileDialog;
if (mUseExistingKeystore.getSelection()) {
fileDialog = new FileDialog(browseButton.getShell(),SWT.OPEN);
fileDialog.setText("Load Keystore");
} else {
fileDialog = new FileDialog(browseButton.getShell(),SWT.SAVE);
fileDialog.setText("Select Keystore Name");
}
String fileName = fileDialog.open();
if (fileName != null) {
@@ -80,64 +101,73 @@ public class SigningExportPage extends ExportWizardPage {
}
}
});
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
gd.horizontalSpan = 2;
gd.heightHint = 0;
new Button(composite, SWT.PUSH).setText("New...");
new Label(composite, SWT.NONE).setText("Key Alias:");
mAlias = new Text(composite, SWT.BORDER);
mAlias.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
new Label(composite, SWT.NONE).setText("Store password:");
new Label(composite, SWT.NONE).setText("Password:");
mKeystorePassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeystorePassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mKeystorePassword.addVerifyListener(sPasswordVerifier);
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
gd.heightHint = gd.widthHint = 0;
new Label(composite, SWT.NONE).setText("Key password:");
mKeyPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeyPassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mConfirmLabel = new Label(composite, SWT.NONE);
mConfirmLabel.setText("Confirm:");
mKeystorePassword2 = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeystorePassword2.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mKeystorePassword2.addVerifyListener(sPasswordVerifier);
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
gd.heightHint = gd.widthHint = 0;
mKeystorePassword2.setEnabled(false);
// Show description the first time
setErrorMessage(null);
setMessage(null);
setControl(composite);
mUseExistingKeystore.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
boolean createStore = !mUseExistingKeystore.getSelection();
mKeystorePassword2.setEnabled(createStore);
mConfirmLabel.setEnabled(createStore);
mWizard.setKeystoreCreationMode(createStore);
onChange();
}
});
mKeystore.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeystore(mKeystore.getText().trim());
onChange();
}
});
mAlias.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeyAlias(mAlias.getText().trim());
onChange();
}
});
mKeystorePassword.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeystorePassword(mKeystorePassword.getText().trim().toCharArray());
mWizard.setKeystorePassword(mKeystorePassword.getText());
onChange();
}
});
mKeyPassword.addModifyListener(new ModifyListener() {
mKeystorePassword2.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeyPassword(mKeyPassword.getText().trim().toCharArray());
onChange();
}
});
}
@Override
public IWizardPage getNextPage() {
if (mUseExistingKeystore.getSelection()) {
return mWizard.getKeySelectionPage();
}
return mWizard.getKeyCreationPage();
}
@Override
void onShow() {
// fill the texts with information loaded from the project.
if (mNewProjectReference) {
if ((mProjectDataChanged & DATA_PROJECT) != 0) {
// reset the keystore/alias from the content of the project
IProject project = mWizard.getProject();
@@ -150,14 +180,9 @@ public class SigningExportPage extends ExportWizardPage {
mKeystore.setText(keystore);
}
String alias = ProjectHelper.loadStringProperty(project, ExportWizard.PROPERTY_ALIAS);
if (alias != null) {
mAlias.setText(alias);
}
// reset the passwords
mKeystorePassword.setText(""); //$NON-NLS-1$
mKeyPassword.setText(""); //$NON-NLS-1$
mKeystorePassword2.setText(""); //$NON-NLS-1$
// enable onChange, and call it to display errors and enable/disable pageCompleted.
mDisableOnChange = false;
@@ -176,6 +201,8 @@ public class SigningExportPage extends ExportWizardPage {
setErrorMessage(null);
setMessage(null);
boolean createStore = !mUseExistingKeystore.getSelection();
// checks the keystore path is non null.
String keystore = mKeystore.getText().trim();
if (keystore.length() == 0) {
@@ -185,34 +212,49 @@ public class SigningExportPage extends ExportWizardPage {
} else {
File f = new File(keystore);
if (f.exists() == false) {
setErrorMessage("Keystore does not exists!");
setPageComplete(false);
return;
if (createStore == false) {
setErrorMessage("Keystore does not exist.");
setPageComplete(false);
return;
}
} else if (f.isDirectory()) {
setErrorMessage("Keystore is a directory!");
setErrorMessage("Keystore path is a directory.");
setPageComplete(false);
return;
} else if (f.isFile()) {
if (createStore) {
setErrorMessage("File already exists.");
setPageComplete(false);
return;
}
}
}
if (mAlias.getText().trim().length() == 0) {
setErrorMessage("Enter key alias.");
setPageComplete(false);
return;
}
if (mKeystorePassword.getText().trim().length() == 0) {
String value = mKeystorePassword.getText();
if (value.length() == 0) {
setErrorMessage("Enter keystore password.");
setPageComplete(false);
return;
}
if (mKeyPassword.getText().trim().length() == 0) {
setErrorMessage("Enter key password.");
} else if (createStore && value.length() < 6) {
setErrorMessage("Keystore password is too short - must be at least 6 characters.");
setPageComplete(false);
return;
}
if (createStore) {
if (mKeystorePassword2.getText().length() == 0) {
setErrorMessage("Confirm keystore password.");
setPageComplete(false);
return;
}
if (mKeystorePassword.getText().equals(mKeystorePassword2.getText()) == false) {
setErrorMessage("Keystore passwords do not match.");
setPageComplete(false);
return;
}
}
setPageComplete(true);
}
}

View File

@@ -47,7 +47,7 @@ import java.io.File;
/**
* First Export Wizard Page. Display warning/errors.
*/
public class PreExportPage extends ExportWizardPage {
final class ProjectCheckPage extends ExportWizardPage {
private final static String IMG_ERROR = "error.png"; //$NON-NLS-1$
private final static String IMG_WARNING = "warning.png"; //$NON-NLS-1$
@@ -60,12 +60,13 @@ public class PreExportPage extends ExportWizardPage {
private Composite mErrorComposite;
private Text mProjectText;
private ProjectChooserHelper mProjectChooserHelper;
private boolean mFirstOnShow = true;
protected PreExportPage(ExportWizard wizard, String pageName) {
protected ProjectCheckPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Pre Export Checks");
setTitle("Project Checks");
setDescription("Performs a set of checks to make sure the application can be exported.");
}
@@ -123,10 +124,14 @@ public class PreExportPage extends ExportWizardPage {
@Override
void onShow() {
// get the project and init the ui
IProject project = mWizard.getProject();
if (project != null) {
mProjectText.setText(project.getName());
if (mFirstOnShow) {
// get the project and init the ui
IProject project = mWizard.getProject();
if (project != null) {
mProjectText.setText(project.getName());
}
mFirstOnShow = false;
}
}

View File

@@ -27,16 +27,19 @@ class AndroidClasspathContainer implements IClasspathContainer {
private IClasspathEntry[] mClasspathEntry;
private IPath mContainerPath;
private String mName;
/**
* Constructs the container with the {@link IClasspathEntry} representing the android
* framework jar file and the container id
* @param entry the entry representing the android framework.
* @param path the path containing the classpath container id.
* @param name the name of the container to display.
*/
AndroidClasspathContainer(IClasspathEntry entry, IPath path) {
AndroidClasspathContainer(IClasspathEntry entry, IPath path, String name) {
mClasspathEntry = new IClasspathEntry[] { entry };
mContainerPath = path;
mName = name;
}
public IClasspathEntry[] getClasspathEntries() {
@@ -44,7 +47,7 @@ class AndroidClasspathContainer implements IClasspathContainer {
}
public String getDescription() {
return "Android Library";
return mName;
}
public int getKind() {

View File

@@ -16,10 +16,16 @@
package com.android.ide.eclipse.adt.project.internal;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.sdk.LoadStatus;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
@@ -38,10 +44,6 @@ import org.eclipse.jdt.core.JavaModelException;
* {@link IProject}s. This removes the hard-coded path to the android.jar.
*/
public class AndroidClasspathContainerInitializer extends ClasspathContainerInitializer {
/** The old container id */
private final static String OLD_CONTAINER_ID =
"com.android.ide.eclipse.adt.project.AndroidClasspathContainerInitializer"; //$NON-NLS-1$
/** The container id for the android framework jar file */
private final static String CONTAINER_ID =
"com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"; //$NON-NLS-1$
@@ -58,17 +60,10 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
*/
@Override
public void initialize(IPath containerPath, IJavaProject project) throws CoreException {
String id = null;
if (OLD_CONTAINER_ID.equals(containerPath.toString())) {
id = OLD_CONTAINER_ID;
} else if (CONTAINER_ID.equals(containerPath.toString())) {
id = CONTAINER_ID;
}
if (id != null) {
JavaCore.setClasspathContainer(new Path(id),
if (CONTAINER_ID.equals(containerPath.toString())) {
JavaCore.setClasspathContainer(new Path(CONTAINER_ID),
new IJavaProject[] { project },
new IClasspathContainer[] { allocateAndroidContainer(id) },
new IClasspathContainer[] { allocateAndroidContainer(CONTAINER_ID, project) },
new NullProgressMonitor());
}
}
@@ -81,15 +76,6 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
return JavaCore.newContainerEntry(new Path(CONTAINER_ID));
}
/**
* Checks the {@link IPath} objects against the old android framework container id and
* returns <code>true</code> if they are identical.
* @param path the <code>IPath</code> to check.
*/
public static boolean checkOldPath(IPath path) {
return OLD_CONTAINER_ID.equals(path.toString());
}
/**
* Checks the {@link IPath} objects against the android framework container id and
* returns <code>true</code> if they are identical.
@@ -106,41 +92,18 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
* @return <code>true</code> if success, <code>false</code> otherwise.
*/
public static boolean updateProjects(IJavaProject[] androidProjects) {
try {
// because those projects could have the old id, we are going to fix
// them dynamically here.
for (IJavaProject javaProject: androidProjects) {
IClasspathEntry[] entries = javaProject.getRawClasspath();
int containerIndex = ProjectHelper.findClasspathEntryByPath(entries,
OLD_CONTAINER_ID,
IClasspathEntry.CPE_CONTAINER);
if (containerIndex != -1) {
// the project has the old container, we remove it
entries = ProjectHelper.removeEntryFromClasspath(entries, containerIndex);
// we add the new one instead
entries = ProjectHelper.addEntryToClasspath(entries, getContainerEntry());
// and give the new entries to the project
javaProject.setRawClasspath(entries, new NullProgressMonitor());
}
}
// Allocate a new AndroidClasspathContainer, and associate it to the android framework
// container id for each projects.
// By providing a new association between a container id and a IClasspathContainer,
// this forces the JDT to query the IClasspathContainer for new IClasspathEntry (with
// IClasspathContainer#getClasspathEntries()), and therefore force recompilation of
// the projects.
// TODO: We could only do that for the projects haven't fixed above
// (this isn't something that will happen a lot though)
int projectCount = androidProjects.length;
IClasspathContainer[] containers = new IClasspathContainer[projectCount];
for (int i = 0 ; i < projectCount; i++) {
containers[i] = allocateAndroidContainer(CONTAINER_ID);
containers[i] = allocateAndroidContainer(CONTAINER_ID, androidProjects[i]);
}
// give each project their new container in one call.
@@ -158,23 +121,123 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
* Allocates and returns an {@link AndroidClasspathContainer} object with the proper
* path to the framework jar file.
* @param containerId the container id to be used.
* @param javaProject The java project that will receive the container.
*/
private static IClasspathContainer allocateAndroidContainer(String containerId) {
return new AndroidClasspathContainer(createFrameworkClasspath(), new Path(containerId));
private static IClasspathContainer allocateAndroidContainer(String containerId,
IJavaProject javaProject) {
IProject iProject = javaProject.getProject();
// remove potential MARKER_TARGETs.
try {
if (iProject.exists()) {
iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true,
IResource.DEPTH_INFINITE);
}
} catch (CoreException ce) {
// just log the error
AdtPlugin.log(ce, "Error removing target marker.");
}
// first we check if the SDK has been loaded
boolean sdkIsLoaded = AdtPlugin.getDefault().getSdkLoadStatus(javaProject) ==
LoadStatus.LOADED;
// then we check if the project has a valid target.
IAndroidTarget target = null;
if (sdkIsLoaded) {
target = Sdk.getCurrent().getTarget(iProject);
}
// if we are loaded and the target is non null, we create a valid ClassPathContainer
if (sdkIsLoaded && target != null) {
String targetName = null;
if (target.isPlatform()) {
targetName = target.getName();
} else {
targetName = String.format("%1$s (%2$s)", target.getName(),
target.getApiVersionName());
}
return new AndroidClasspathContainer(createFrameworkClasspath(target),
new Path(containerId), targetName);
}
// else we put a marker on the project, and return a dummy container (to replace the
// previous one if there was one.)
// Get the project's target's hash string (if it exists)
String hashString = Sdk.getProjectTargetHashString(iProject);
String message = null;
boolean outputToConsole = true;
if (hashString == null || hashString.length() == 0) {
message = String.format(
"Project has no target set. Edit the project properties to set one.");
} else if (sdkIsLoaded) {
message = String.format(
"Unable to resolve target '%s'", hashString);
} else {
// this is the case where there is a hashString but the SDK is not yet
// loaded and therefore we can't get the target yet.
message = String.format(
"Unable to resolve target '%s' until the SDK is loaded.", hashString);
// let's not log this one to the console as it will happen at every boot,
// and it's expected. (we do keep the error marker though).
outputToConsole = false;
}
// log the error and put the marker on the project
if (outputToConsole) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_ALWAYS, iProject, message);
}
IMarker marker = BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, message,
IMarker.SEVERITY_ERROR);
// add a marker priority as this is an more important error than the error that will
// spring from the lack of library
try {
marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
} catch (CoreException e) {
// just log the error
AdtPlugin.log(e, "Error changing target marker priority.");
}
// return a dummy container to replace the one we may have had before.
return new IClasspathContainer() {
public IClasspathEntry[] getClasspathEntries() {
return new IClasspathEntry[0];
}
public String getDescription() {
return "Unable to get system library for the project";
}
public int getKind() {
return IClasspathContainer.K_DEFAULT_SYSTEM;
}
public IPath getPath() {
return null;
}
};
}
/**
* Creates and returns a new {@link IClasspathEntry} object for the android framework.
* <p/>This references the OS path to the android.jar and the java doc directory. This is
* dynamically created when a project is opened, and never saved in the project itself, so
* there's no risk of storing an obsolete path.
* Creates and returns a new {@link IClasspathEntry} object for the android
* framework. <p/>This references the OS path to the android.jar and the
* java doc directory. This is dynamically created when a project is opened,
* and never saved in the project itself, so there's no risk of storing an
* obsolete path.
*
* @param target The target that contains the libraries.
*/
private static IClasspathEntry createFrameworkClasspath() {
private static IClasspathEntry createFrameworkClasspath(IAndroidTarget target) {
// now add the android framework to the class path.
// create the path object.
IPath android_lib = new Path(AdtPlugin.getOsAbsoluteFramework());
IPath android_src = new Path(AdtPlugin.getOsAbsoluteAndroidSources());
IPath android_lib = new Path(target.getPath(IAndroidTarget.ANDROID_JAR));
IPath android_src = new Path(target.getPath(IAndroidTarget.SOURCES));
// create the java doc link.
IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute(

View File

@@ -0,0 +1,98 @@
/*
* 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.project.properties;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.sdklib.IAndroidTarget;
import com.android.sdkuilib.SdkTargetSelector;
import org.eclipse.core.resources.IProject;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IWorkbenchPropertyPage;
import org.eclipse.ui.dialogs.PropertyPage;
/**
* Property page for "Android" project.
* This is accessible from the Package Explorer when right clicking a project and choosing
* "Properties".
*
*/
public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPropertyPage {
private IProject mProject;
private SdkTargetSelector mSelector;
public AndroidPropertyPage() {
// pass
}
@Override
protected Control createContents(Composite parent) {
// get the element (this is not yet valid in the constructor).
mProject = (IProject)getElement();
Composite top = new Composite(parent, SWT.NONE);
top.setLayoutData(new GridData(GridData.FILL_BOTH));
top.setLayout(new GridLayout(1, false));
Label l = new Label(top, SWT.NONE);
l.setText("Project Target");
// get the targets from the sdk
IAndroidTarget[] targets = null;
if (Sdk.getCurrent() != null) {
targets = Sdk.getCurrent().getTargets();
}
// build the UI.
mSelector = new SdkTargetSelector(top, targets, false /*allowMultipleSelection*/);
if (Sdk.getCurrent() != null) {
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
if (target != null) {
mSelector.setSelection(target);
}
}
mSelector.setSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// look for the selection and validate the page if there is a selection
IAndroidTarget target = mSelector.getFirstSelected();
setValid(target != null);
}
});
return top;
}
@Override
public boolean performOk() {
if (Sdk.getCurrent() != null) {
Sdk.getCurrent().setProject(mProject, mSelector.getFirstSelected());
}
return true;
}
}

View File

@@ -14,9 +14,8 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.resources;
package com.android.ide.eclipse.adt.sdk;
import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass;
import com.android.ide.eclipse.common.AndroidConstants;
import org.eclipse.core.runtime.IProgressMonitor;
@@ -35,9 +34,12 @@ import javax.management.InvalidAttributeValueException;
/**
* Custom class loader able to load a class from the SDK jar file.
*/
public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader {
public final static class ClassWrapper implements IClass {
/**
* Wrapper around a {@link Class} to provide the methods of {@link IClassDescriptor}.
*/
public final static class ClassWrapper implements IClassDescriptor {
private Class<?> mClass;
public ClassWrapper(Class<?> clazz) {
@@ -48,9 +50,9 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
return mClass.getCanonicalName();
}
public IClass[] getDeclaredClasses() {
public IClassDescriptor[] getDeclaredClasses() {
Class<?>[] classes = mClass.getDeclaredClasses();
IClass[] iclasses = new IClass[classes.length];
IClassDescriptor[] iclasses = new IClassDescriptor[classes.length];
for (int i = 0 ; i < classes.length ; i++) {
iclasses[i] = new ClassWrapper(classes[i]);
}
@@ -58,7 +60,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
return iclasses;
}
public IClass getEnclosingClass() {
public IClassDescriptor getEnclosingClass() {
return new ClassWrapper(mClass.getEnclosingClass());
}
@@ -66,7 +68,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
return mClass.getSimpleName();
}
public IClass getSuperclass() {
public IClassDescriptor getSuperclass() {
return new ClassWrapper(mClass.getSuperclass());
}
@@ -131,17 +133,18 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
* @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, IProgressMonitor monitor)
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, 100);
SubMonitor progress = SubMonitor.convert(monitor, taskLabel == null ? "" : taskLabel, 100);
// create streams to read the intermediary archive
FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
@@ -174,6 +177,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
// advance 5% of whatever is allocated on the progress bar
progress.setWorkRemaining(100);
progress.worked(5);
progress.subTask(String.format("Preload %1$s", className));
}
}
@@ -193,17 +197,18 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
* @throws InvalidAttributeValueException
* @throws ClassFormatError
*/
public HashMap<String, ArrayList<IClass>> findClassesDerivingFrom(
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<IClass>> mClassesFound = new HashMap<String, ArrayList<IClass>>();
HashMap<String, ArrayList<IClassDescriptor>> mClassesFound =
new HashMap<String, ArrayList<IClassDescriptor>>();
for (String className : superClasses) {
mClassesFound.put(className, new ArrayList<IClass>());
mClassesFound.put(className, new ArrayList<IClassDescriptor>());
}
// create streams to read the intermediary archive
@@ -415,7 +420,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
* @param className the fully-qualified name of the class to return.
* @throws ClassNotFoundException
*/
public IClass getClass(String className) throws ClassNotFoundException {
public IClassDescriptor getClass(String className) throws ClassNotFoundException {
try {
return new ClassWrapper(loadClass(className));
} catch (ClassNotFoundException e) {

View File

@@ -0,0 +1,285 @@
/*
* 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.sdk;
import com.android.ide.eclipse.common.resources.IResourceRepository;
import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider;
import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors;
import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors;
import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors;
import com.android.ide.eclipse.editors.resources.manager.ProjectResources;
import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors;
import com.android.layoutlib.api.ILayoutBridge;
import com.android.sdklib.IAndroidTarget;
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_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 class LayoutBridge {
/** Link to the layout bridge */
public ILayoutBridge bridge;
public LoadStatus status = LoadStatus.LOADING;
public ClassLoader classLoader;
}
private final IAndroidTarget mTarget;
/**
* mAttributeValues is a map { key => list [ values ] }.
* The key for the map is "(element-xml-name,attribute-namespace:attribute-xml-local-name)".
* The attribute namespace prefix must be:
* - "android" for AndroidConstants.NS_RESOURCES
* - "xmlns" for the XMLNS URI.
*
* This is used for attributes that do not have a unique name, but still need to be populated
* with values in the UI. Uniquely named attributes have their values in {@link #mEnumValueMap}.
*/
private final Hashtable<String, String[]> mAttributeValues = new Hashtable<String, String[]>();
private IResourceRepository mSystemResourceRepository;
private final AndroidManifestDescriptors mManifestDescriptors;
private final LayoutDescriptors mLayoutDescriptors;
private final MenuDescriptors mMenuDescriptors;
private final XmlDescriptors mXmlDescriptors;
private final Map<String, Map<String, Integer>> mEnumValueMap;
private final ProjectResources mFrameworkResources;
private final LayoutBridge mLayoutBridge;
private boolean mLayoutBridgeInit = false;
/**
* Creates an AndroidTargetData object.
*/
AndroidTargetData(IAndroidTarget androidTarget,
IResourceRepository systemResourceRepository,
AndroidManifestDescriptors manifestDescriptors,
LayoutDescriptors layoutDescriptors,
MenuDescriptors menuDescriptors,
XmlDescriptors xmlDescriptors,
Map<String, Map<String, Integer>> enumValueMap,
String[] permissionValues,
String[] activityIntentActionValues,
String[] broadcastIntentActionValues,
String[] serviceIntentActionValues,
String[] intentCategoryValues,
ProjectResources resources,
LayoutBridge layoutBridge) {
mTarget = androidTarget;
mSystemResourceRepository = systemResourceRepository;
mManifestDescriptors = manifestDescriptors;
mLayoutDescriptors = layoutDescriptors;
mMenuDescriptors = menuDescriptors;
mXmlDescriptors = xmlDescriptors;
mEnumValueMap = enumValueMap;
mFrameworkResources = resources;
mLayoutBridge = layoutBridge;
setPermissions(permissionValues);
setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues,
serviceIntentActionValues, intentCategoryValues);
}
public IResourceRepository getSystemResources() {
return mSystemResourceRepository;
}
/**
* Returns an {@link IDescriptorProvider} from a given Id.
* The Id can be one of {@link #DESCRIPTOR_MANIFEST}, {@link #DESCRIPTOR_LAYOUT},
* {@link #DESCRIPTOR_MENU}, or {@link #DESCRIPTOR_XML}.
* All other values will throw an {@link IllegalArgumentException}.
*/
public IDescriptorProvider getDescriptorProvider(int descriptorId) {
switch (descriptorId) {
case DESCRIPTOR_MANIFEST:
return mManifestDescriptors;
case DESCRIPTOR_LAYOUT:
return mLayoutDescriptors;
case DESCRIPTOR_MENU:
return mMenuDescriptors;
case DESCRIPTOR_XML:
return mXmlDescriptors;
case DESCRIPTOR_RESOURCES:
// FIXME: since it's hard-coded the Resources Descriptors are not platform dependent.
return ResourcesDescriptors.getInstance();
case DESCRIPTOR_PREFERENCES:
return mXmlDescriptors.getPreferencesProvider();
case DESCRIPTOR_SEARCHABLE:
return mXmlDescriptors.getSearchableProvider();
default :
throw new IllegalArgumentException();
}
}
/**
* Returns the manifest descriptors.
*/
public AndroidManifestDescriptors getManifestDescriptors() {
return mManifestDescriptors;
}
/**
* Returns the layout Descriptors.
*/
public LayoutDescriptors getLayoutDescriptors() {
return mLayoutDescriptors;
}
/**
* Returns the menu descriptors.
*/
public MenuDescriptors getMenuDescriptors() {
return mMenuDescriptors;
}
/**
* Returns the XML descriptors
*/
public XmlDescriptors getXmlDescriptors() {
return mXmlDescriptors;
}
/**
* Returns this list of possible values for an XML attribute.
* <p/>This should only be called for attributes for which possible values depend on the
* parent element node.
* <p/>For attributes that have the same values no matter the parent node, use
* {@link #getEnumValueMap()}.
* @param elementName the name of the element containing the attribute.
* @param attributeName the name of the attribute
* @return an array of String with the possible values, or <code>null</code> if no values were
* found.
*/
public String[] getAttributeValues(String elementName, String attributeName) {
String key = String.format("(%1$s,%2$s)", elementName, attributeName); //$NON-NLS-1$
return mAttributeValues.get(key);
}
/**
* Returns this list of possible values for an XML attribute.
* <p/>This should only be called for attributes for which possible values depend on the
* parent and great-grand-parent element node.
* <p/>The typical example of this is for the 'name' attribute under
* activity/intent-filter/action
* <p/>For attributes that have the same values no matter the parent node, use
* {@link #getEnumValueMap()}.
* @param elementName the name of the element containing the attribute.
* @param attributeName the name of the attribute
* @param greatGrandParentElementName the great-grand-parent node.
* @return an array of String with the possible values, or <code>null</code> if no values were
* found.
*/
public String[] getAttributeValues(String elementName, String attributeName,
String greatGrandParentElementName) {
if (greatGrandParentElementName != null) {
String key = String.format("(%1$s,%2$s,%3$s)", //$NON-NLS-1$
greatGrandParentElementName, elementName, attributeName);
String[] values = mAttributeValues.get(key);
if (values != null) {
return values;
}
}
return getAttributeValues(elementName, attributeName);
}
/**
* Returns the enum values map.
* <p/>The map defines the possible values for XML attributes. The key is the attribute name
* and the value is a map of (string, integer) in which the key (string) is the name of
* the value, and the Integer is the numerical value in the compiled binary XML files.
*/
public Map<String, Map<String, Integer>> getEnumValueMap() {
return mEnumValueMap;
}
/**
* Returns the {@link ProjectResources} containing the Framework Resources.
*/
public ProjectResources getFrameworkResources() {
return mFrameworkResources;
}
/**
* Returns a {@link LayoutBridge} object possibly containing a {@link ILayoutBridge} object.
* <p/>If {@link LayoutBridge#bridge} is <code>null</code>, {@link LayoutBridge#status} will
* contain the reason (either {@link LoadStatus#LOADING} or {@link LoadStatus#FAILED}).
* <p/>Valid {@link ILayoutBridge} objects are always initialized before being returned.
*/
public synchronized LayoutBridge getLayoutBridge() {
if (mLayoutBridgeInit == false && mLayoutBridge.bridge != null) {
mLayoutBridge.bridge.init(mTarget.getPath(IAndroidTarget.FONTS),
getEnumValueMap());
mLayoutBridgeInit = true;
}
return mLayoutBridge;
}
/**
* 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$
}
/**
* 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);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2007 The Android Open Source Project
* 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.
@@ -14,19 +14,29 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.resources;
package com.android.ide.eclipse.adt.sdk;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.resources.AttrsXmlParser;
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo;
import com.android.ide.eclipse.common.resources.FrameworkResourceManager;
import com.android.ide.eclipse.common.resources.IResourceRepository;
import com.android.ide.eclipse.common.resources.ResourceItem;
import com.android.ide.eclipse.common.resources.ResourceType;
import com.android.ide.eclipse.common.resources.ViewClassInfo;
import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors;
import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors;
import com.android.ide.eclipse.editors.resources.manager.ProjectResources;
import com.android.ide.eclipse.editors.resources.manager.ResourceManager;
import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors;
import com.android.layoutlib.api.ILayoutBridge;
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;
@@ -34,10 +44,11 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -48,7 +59,7 @@ import java.util.Map;
import javax.management.InvalidAttributeValueException;
/**
* Parser for the framework library.
* Parser for the platform data in an SDK.
* <p/>
* This gather the following information:
* <ul>
@@ -57,14 +68,16 @@ import javax.management.InvalidAttributeValueException;
* <li></li>
* </ul>
*/
public final class FrameworkResourceParser {
public final class AndroidTargetParser {
private static final String TAG = "Framework Resource Parser";
private final IAndroidTarget mAndroidTarget;
/**
* Creates a framework resource parser.
* Creates a platform data parser.
*/
public FrameworkResourceParser() {
public AndroidTargetParser(IAndroidTarget platformTarget) {
mAndroidTarget = platformTarget;
}
/**
@@ -77,34 +90,29 @@ public final class FrameworkResourceParser {
* @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 boolean parse(String osSdkPath, FrameworkResourceManager resourceManager,
IProgressMonitor monitor) {
if (osSdkPath == null || osSdkPath.length() == 0) {
return false;
}
public IStatus run(IProgressMonitor monitor) {
try {
SubMonitor progress = SubMonitor.convert(monitor, 100);
SubMonitor progress = SubMonitor.convert(monitor,
String.format("Parsing SDK %1$s", mAndroidTarget.getName()),
120);
AndroidJarLoader classLoader =
new AndroidJarLoader(osSdkPath + AndroidConstants.FN_FRAMEWORK_LIBRARY);
new AndroidJarLoader(mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
progress.subTask("Preloading");
preload(classLoader, progress.newChild(40));
progress.setWorkRemaining(60);
preload(classLoader, progress.newChild(40, SubMonitor.SUPPRESS_NONE));
progress.setWorkRemaining(80);
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
// get the resource Ids.
progress.subTask("Resource IDs");
FrameworkResourceRepository systemResourceRepository = new FrameworkResourceRepository(
collectResourceIds(classLoader));
IResourceRepository frameworkRepository = collectResourceIds(classLoader);
progress.worked(5);
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
// get the permissions
@@ -113,56 +121,59 @@ public final class FrameworkResourceParser {
progress.worked(5);
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
String osLibPath = osSdkPath + AndroidConstants.OS_SDK_LIBS_FOLDER;
// 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(osLibPath,
activity_actions, broadcast_actions, service_actions, categories);
collectIntentFilterActionsAndCategories(activity_actions, broadcast_actions,
service_actions, categories);
progress.worked(5);
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
progress.subTask("Layouts");
// gather the attribute definition
progress.subTask("Attributes definitions");
AttrsXmlParser attrsXmlParser = new AttrsXmlParser(
osSdkPath + AndroidConstants.OS_SDK_ATTRS_XML);
mAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES));
attrsXmlParser.preload();
progress.subTask("Manifest definitions");
AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser(
osSdkPath + AndroidConstants.OS_SDK_ATTRS_MANIFEST_XML,
mAndroidTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES),
attrsXmlParser);
attrsManifestXmlParser.preload();
Collection<ViewClassInfo> mainList = new ArrayList<ViewClassInfo>();
Collection<ViewClassInfo> groupList = new ArrayList<ViewClassInfo>();
collectLayoutClasses(osLibPath, classLoader, attrsXmlParser, mainList, groupList,
// collect the layout/widgets classes
progress.subTask("Widgets and layouts");
collectLayoutClasses(classLoader, attrsXmlParser, mainList, groupList,
progress.newChild(40));
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
ViewClassInfo[] layoutViewsInfo = mainList.toArray(new ViewClassInfo[mainList.size()]);
ViewClassInfo[] layoutGroupsInfo = groupList.toArray(
new ViewClassInfo[groupList.size()]);
// collect the preferences classes.
mainList.clear();
groupList.clear();
collectPreferenceClasses(classLoader, attrsXmlParser, mainList, groupList,
progress.newChild(5));
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
ViewClassInfo[] preferencesInfo = mainList.toArray(new ViewClassInfo[mainList.size()]);
@@ -177,34 +188,77 @@ public final class FrameworkResourceParser {
Map<String, Map<String, Integer>> enumValueMap = attrsXmlParser.getEnumFlagValues();
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
String docBaseUrl = getDocumentationBaseUrl(
osSdkPath + AndroidConstants.OS_SDK_DOCS_FOLDER);
// 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(10);
FrameworkResourceManager.getInstance().setResources(systemResourceRepository,
layoutViewsInfo,
layoutGroupsInfo,
preferencesInfo,
preferenceGroupsInfo,
xmlMenuMap,
xmlSearchableMap,
manifestMap,
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
LayoutDescriptors layoutDescriptors = new LayoutDescriptors();
layoutDescriptors.updateDescriptors(layoutViewsInfo, layoutGroupsInfo);
progress.worked(10);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
MenuDescriptors menuDescriptors = new MenuDescriptors();
menuDescriptors.updateDescriptors(xmlMenuMap);
progress.worked(10);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
XmlDescriptors xmlDescriptors = new XmlDescriptors();
xmlDescriptors.updateDescriptors(xmlSearchableMap, preferencesInfo,
preferenceGroupsInfo);
progress.worked(10);
// load the framework resources.
ProjectResources resources = ResourceManager.getInstance().loadFrameworkResources(
mAndroidTarget);
progress.worked(10);
// now load the layout lib bridge
LayoutBridge layoutBridge = loadLayoutBridge();
progress.worked(10);
// and finally create the PlatformData with all that we loaded.
AndroidTargetData targetData = new AndroidTargetData(mAndroidTarget,
frameworkRepository,
manifestDescriptors,
layoutDescriptors,
menuDescriptors,
xmlDescriptors,
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()]),
docBaseUrl);
resources,
layoutBridge);
Sdk.getCurrent().setTargetData(mAndroidTarget, targetData);
return true;
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);
} finally {
if (monitor != null) {
monitor.done();
}
}
return false;
}
/**
@@ -217,7 +271,9 @@ public final class FrameworkResourceParser {
*/
private void preload(AndroidJarLoader classLoader, IProgressMonitor monitor) {
try {
classLoader.preLoadClasses("" /* all classes */, monitor); //$NON-NLS-1$
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) {
@@ -226,24 +282,27 @@ public final class FrameworkResourceParser {
}
/**
* Collects the resources IDs found in the SDK.
* Creates an IResourceRepository for the framework resources.
*
* @param classLoader The framework SDK jar classloader
* @return a map of the resources, or null if it failed.
*/
private Map<ResourceType, List<ResourceItem>> collectResourceIds(
private IResourceRepository collectResourceIds(
AndroidJarLoader classLoader) {
try {
Class<?> r = classLoader.loadClass(AndroidConstants.CLASS_R);
if (r != null) {
return parseRClass(r);
Map<ResourceType, List<ResourceItem>> map = parseRClass(r);
if (map != null) {
return new FrameworkResourceRepository(map);
}
}
} catch (ClassNotFoundException e) {
AdtPlugin.logAndPrintError(e, TAG,
"Collect resource IDs failed, class %1$s not found in %2$s", //$NON-NLS-1$
AndroidConstants.CLASS_R,
classLoader.getSource());
mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
}
return null;
@@ -331,7 +390,7 @@ public final class FrameworkResourceParser {
AdtPlugin.logAndPrintError(e, TAG,
"Collect permissions failed, class %1$s not found in %2$s", //$NON-NLS-1$
AndroidConstants.CLASS_MANIFEST_PERMISSION,
classLoader.getSource());
mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
}
return new String[0];
@@ -347,13 +406,17 @@ public final class FrameworkResourceParser {
* @param serviceActions the list which will receive the service action values.
* @param categories the list which will receive the category values.
*/
private void collectIntentFilterActionsAndCategories(String osLibPath,
ArrayList<String> activityActions, ArrayList<String> broadcastActions,
private void collectIntentFilterActionsAndCategories(ArrayList<String> activityActions,
ArrayList<String> broadcastActions,
ArrayList<String> serviceActions, ArrayList<String> categories) {
collectValues(osLibPath + "activity_actions.txt" , activityActions);
collectValues(osLibPath + "broadcast_actions.txt" , broadcastActions);
collectValues(osLibPath + "service_actions.txt" , serviceActions);
collectValues(osLibPath + "categories.txt" , 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);
}
/**
@@ -400,21 +463,21 @@ public final class FrameworkResourceParser {
* Collects all layout classes information from the class loader and the
* attrs.xml and sets the corresponding structures in the resource manager.
*
* @param osLibPath The OS path to the SDK tools/lib folder, ending with a separator.
* @param classLoader The framework SDK jar classloader
* @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(String osLibPath,
AndroidJarLoader classLoader,
private void collectLayoutClasses(AndroidJarLoader classLoader,
AttrsXmlParser attrsXmlParser,
Collection<ViewClassInfo> mainList, Collection<ViewClassInfo> groupList,
IProgressMonitor monitor) {
LayoutParamsParser ldp = null;
try {
WidgetListLoader loader = new WidgetListLoader(osLibPath + "widgets.txt");
WidgetClassLoader loader = new WidgetClassLoader(
mAndroidTarget.getPath(IAndroidTarget.WIDGETS));
if (loader.parseWidgetList(monitor)) {
ldp = new LayoutParamsParser(loader, attrsXmlParser);
}
@@ -465,12 +528,13 @@ public final class FrameworkResourceParser {
}
} catch (NoClassDefFoundError e) {
AdtPlugin.logAndPrintError(e, TAG,
"Collect preferences failed, class %1$s not found in %2$s", //$NON-NLS-1$
"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");
AdtPlugin.printErrorToConsole("Android Framework Parser",
"failed to collect preference classes");
}
}
@@ -537,40 +601,51 @@ public final class FrameworkResourceParser {
}
/**
* 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.
* Loads the layout bridge from the dynamically loaded layoutlib.jar
*/
private String getDocumentationBaseUrl(String osDocsPath) {
File f = new File(osDocsPath);
private LayoutBridge loadLayoutBridge() {
LayoutBridge layoutBridge = new LayoutBridge();
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.
try {
// get the URL for the file.
File f = new File(mAndroidTarget.getPath(IAndroidTarget.LAYOUT_LIB));
if (f.isFile() == false) {
AdtPlugin.log(IStatus.ERROR, "layoutlib.jar is missing!"); //$NON-NLS-1$
} else {
URL url = f.toURL();
String path = f.getAbsolutePath();
if (File.separatorChar != '/') {
path = path.replace(File.separatorChar, '/');
// create a class loader. Because this jar reference interfaces
// that are in the editors plugin, it's important to provide
// a parent class loader.
layoutBridge.classLoader = new URLClassLoader(new URL[] { url },
this.getClass().getClassLoader());
// load the class
Class<?> clazz = layoutBridge.classLoader.loadClass(AndroidConstants.CLASS_BRIDGE);
if (clazz != null) {
// instantiate an object of the class.
Constructor<?> constructor = clazz.getConstructor();
if (constructor != null) {
Object bridge = constructor.newInstance();
if (bridge instanceof ILayoutBridge) {
layoutBridge.bridge = (ILayoutBridge)bridge;
}
}
}
// 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
if (layoutBridge.bridge == null) {
layoutBridge.status = LoadStatus.FAILED;
AdtPlugin.log(IStatus.ERROR, "Failed to load " + AndroidConstants.CLASS_BRIDGE); //$NON-NLS-1$
} else {
layoutBridge.status = LoadStatus.LOADED;
}
}
} catch (Throwable t) {
layoutBridge.status = LoadStatus.FAILED;
// log the error.
AdtPlugin.log(t, "Failed to load the LayoutLib");
}
return null;
return layoutBridge;
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.resources;
package com.android.ide.eclipse.adt.sdk;
import com.android.ide.eclipse.common.resources.IResourceRepository;
import com.android.ide.eclipse.common.resources.ResourceItem;
@@ -26,9 +26,9 @@ import java.util.Set;
/**
* Implementation of the {@link IResourceRepository} interface to hold the system resource Ids
* parsed by {@link FrameworkResourceParser}.
* parsed by {@link AndroidTargetParser}.
*/
public final class FrameworkResourceRepository implements IResourceRepository {
final class FrameworkResourceRepository implements IResourceRepository {
private Map<ResourceType, List<ResourceItem>> mResourcesMap;

View File

@@ -14,9 +14,7 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.resources;
import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass;
package com.android.ide.eclipse.adt.sdk;
import java.io.IOException;
import java.util.ArrayList;
@@ -28,7 +26,25 @@ import javax.management.InvalidAttributeValueException;
* Classes which implements this interface provide methods to access framework resource
* data loaded from the SDK.
*/
public interface IAndroidLoader {
public interface IAndroidClassLoader {
/**
* Classes which implement this interface provide methods to describe a class.
*/
public interface IClassDescriptor {
String getCanonicalName();
IClassDescriptor getSuperclass();
String getSimpleName();
IClassDescriptor getEnclosingClass();
IClassDescriptor[] getDeclaredClasses();
boolean isInstantiable();
}
/**
* Finds and loads all classes that derive from a given set of super classes.
@@ -43,7 +59,7 @@ public interface IAndroidLoader {
* @throws InvalidAttributeValueException
* @throws ClassFormatError
*/
public HashMap<String, ArrayList<IClass>> findClassesDerivingFrom(
public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(
String rootPackage, String[] superClasses)
throws IOException, InvalidAttributeValueException, ClassFormatError;
@@ -52,7 +68,7 @@ public interface IAndroidLoader {
* @param className the fully-qualified name of the class to return.
* @throws ClassNotFoundException
*/
public IClass getClass(String className) throws ClassNotFoundException;
public IClassDescriptor getClass(String className) throws ClassNotFoundException;
/**
* Returns a string indicating the source of the classes, typically for debugging

View File

@@ -14,10 +14,11 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.resources;
package com.android.ide.eclipse.adt.sdk;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.IAndroidClassLoader.IClassDescriptor;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.CommonPlugin;
import com.android.ide.eclipse.common.resources.AttrsXmlParser;
import com.android.ide.eclipse.common.resources.ViewClassInfo;
import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo;
@@ -52,23 +53,10 @@ import javax.management.InvalidAttributeValueException;
public class LayoutParamsParser {
/**
* Classes which implement this interface provide methods to describe a class.
* 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.
*/
public interface IClass {
public String getCanonicalName();
public IClass getSuperclass();
public String getSimpleName();
public IClass getEnclosingClass();
public IClass[] getDeclaredClasses();
public boolean isInstantiable();
}
final static class ExtViewClassInfo extends ViewClassInfo {
private boolean mIsInstantiable;
@@ -87,16 +75,16 @@ public class LayoutParamsParser {
/* Note: protected members/methods are overridden in unit tests */
/** Reference to android.view.View */
protected IClass mTopViewClass;
protected IClassDescriptor mTopViewClass;
/** Reference to android.view.ViewGroup */
protected IClass mTopGroupClass;
protected IClassDescriptor mTopGroupClass;
/** Reference to android.view.ViewGroup$LayoutParams */
protected IClass mTopLayoutParamsClass;
protected IClassDescriptor mTopLayoutParamsClass;
/** Input list of all classes deriving from android.view.View */
protected ArrayList<IClass> mViewList;
protected ArrayList<IClassDescriptor> mViewList;
/** Input list of all classes deriving from android.view.ViewGroup */
protected ArrayList<IClass> mGroupList;
protected ArrayList<IClassDescriptor> mGroupList;
/** Output map of FQCN => info on View classes */
protected TreeMap<String, ExtViewClassInfo> mViewMap;
@@ -109,14 +97,14 @@ public class LayoutParamsParser {
protected AttrsXmlParser mAttrsXmlParser;
/** The android.jar class loader */
protected IAndroidLoader mClassLoader;
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(IAndroidLoader classLoader,
public LayoutParamsParser(IAndroidClassLoader classLoader,
AttrsXmlParser attrsXmlParser) {
mClassLoader = classLoader;
mAttrsXmlParser = attrsXmlParser;
@@ -135,8 +123,8 @@ public class LayoutParamsParser {
/**
* TODO: doc here.
* <p/>
* Note: on output we should have NO dependency on IClass, otherwise we wouldn't be able
* to unload the class loader later.
* 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.
@@ -168,8 +156,8 @@ public class LayoutParamsParser {
if (paramsClassName != null) {
superClasses[2] = paramsClassName;
}
HashMap<String, ArrayList<IClass>> found = mClassLoader.findClassesDerivingFrom(
"android.", superClasses);
HashMap<String, ArrayList<IClassDescriptor>> found =
mClassLoader.findClassesDerivingFrom("android.", superClasses);
mTopViewClass = mClassLoader.getClass(rootClassName);
mTopGroupClass = mClassLoader.getClass(groupClassName);
if (paramsClassName != null) {
@@ -196,26 +184,26 @@ public class LayoutParamsParser {
progress.setWorkRemaining(mGroupList.size() + mViewList.size());
for (IClass groupChild : mGroupList) {
for (IClassDescriptor groupChild : mGroupList) {
addGroup(groupChild);
progress.worked(1);
}
for (IClass viewChild : mViewList) {
for (IClassDescriptor viewChild : mViewList) {
if (viewChild != mTopGroupClass) {
addView(viewChild);
}
progress.worked(1);
}
} catch (ClassNotFoundException e) {
CommonPlugin.log(e, "Problem loading class %1$s or %2$s", //$NON-NLS-1$
AdtPlugin.log(e, "Problem loading class %1$s or %2$s", //$NON-NLS-1$
rootClassName, groupClassName);
} catch (InvalidAttributeValueException e) {
CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
} catch (ClassFormatError e) {
CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
} catch (IOException e) {
CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
}
}
@@ -223,7 +211,7 @@ public class LayoutParamsParser {
* 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(IClass viewClass) {
private ExtViewClassInfo addView(IClassDescriptor viewClass) {
String fqcn = viewClass.getCanonicalName();
if (mViewMap.containsKey(fqcn)) {
return mViewMap.get(fqcn);
@@ -238,7 +226,7 @@ public class LayoutParamsParser {
// All view classes derive from mTopViewClass by design.
// Do not lookup the super class for mTopViewClass itself.
if (viewClass.equals(mTopViewClass) == false) {
IClass superClass = viewClass.getSuperclass();
IClassDescriptor superClass = viewClass.getSuperclass();
ExtViewClassInfo superClassInfo = addView(superClass);
info.setSuperClass(superClassInfo);
}
@@ -251,7 +239,7 @@ public class LayoutParamsParser {
* 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(IClass groupClass) {
private ExtViewClassInfo addGroup(IClassDescriptor groupClass) {
String fqcn = groupClass.getCanonicalName();
if (mGroupMap.containsKey(fqcn)) {
return mGroupMap.get(fqcn);
@@ -265,7 +253,7 @@ public class LayoutParamsParser {
// 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.
IClass superClass = groupClass.getSuperclass();
IClassDescriptor superClass = groupClass.getSuperclass();
// Assertion: at this point, we should have
// superClass != mTopViewClass || fqcn.equals(AndroidConstants.CLASS_VIEWGROUP);
@@ -291,15 +279,15 @@ public class LayoutParamsParser {
*
* @return The {@link LayoutParamsInfo} for the ViewGroup class or null.
*/
private LayoutParamsInfo addLayoutParams(IClass groupClass) {
private LayoutParamsInfo addLayoutParams(IClassDescriptor groupClass) {
// Is there a LayoutParams in this group class?
IClass layoutParamsClass = findLayoutParams(groupClass);
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 (IClass superClass = groupClass.getSuperclass();
for (IClassDescriptor superClass = groupClass.getSuperclass();
layoutParamsClass == null &&
superClass != null &&
superClass.equals(mTopViewClass) == false;
@@ -319,7 +307,7 @@ public class LayoutParamsParser {
* 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(IClass layoutParamsClass) {
private LayoutParamsInfo getLayoutParamsInfo(IClassDescriptor layoutParamsClass) {
String fqcn = layoutParamsClass.getCanonicalName();
LayoutParamsInfo layoutParamsInfo = mLayoutParamsMap.get(fqcn);
@@ -330,7 +318,7 @@ public class LayoutParamsParser {
// Find the link on the LayoutParams super class
LayoutParamsInfo superClassInfo = null;
if (layoutParamsClass.equals(mTopLayoutParamsClass) == false) {
IClass superClass = layoutParamsClass.getSuperclass();
IClassDescriptor superClass = layoutParamsClass.getSuperclass();
superClassInfo = getLayoutParamsInfo(superClass);
}
@@ -355,9 +343,9 @@ public class LayoutParamsParser {
* @param groupClass The ViewGroup derived class
* @return The Class of the inner LayoutParams or null if none is declared.
*/
private IClass findLayoutParams(IClass groupClass) {
IClass[] innerClasses = groupClass.getDeclaredClasses();
for (IClass innerClass : innerClasses) {
private IClassDescriptor findLayoutParams(IClassDescriptor groupClass) {
IClassDescriptor[] innerClasses = groupClass.getDeclaredClasses();
for (IClassDescriptor innerClass : innerClasses) {
if (innerClass.getSimpleName().equals(AndroidConstants.CLASS_LAYOUTPARAMS)) {
return innerClass;
}
@@ -365,12 +353,16 @@ public class LayoutParamsParser {
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.mIsInstantiable) {
if (info.isInstantiable()) {
list.add(info);
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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.sdk;
/**
* Enum for loading status of various SDK parts.
*/
public enum LoadStatus {
LOADING, LOADED, FAILED;
}

View File

@@ -0,0 +1,282 @@
/*
* 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.sdk;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.android.sdklib.project.ProjectProperties;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
/**
* 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 present in the SDK, call {@link #getPlatforms()}.
* To get the list of add-ons present in the SDK, call {@link #getAddons()}.
*
*/
public class Sdk {
private final static String PROPERTY_PROJECT_TARGET = "androidTarget"; //$NON-NLS-1$
private static Sdk sCurrentSdk = null;
private final SdkManager mManager;
private final HashMap<IProject, IAndroidTarget> mProjectMap =
new HashMap<IProject, IAndroidTarget>();
private final HashMap<IAndroidTarget, AndroidTargetData> mTargetMap =
new HashMap<IAndroidTarget, AndroidTargetData>();
private final String mDocBaseUrl;
/**
* Loads an SDK and returns an {@link Sdk} object if success.
* @param sdkLocation the OS path to the SDK.
*/
public static Sdk loadSdk(String sdkLocation) {
if (sCurrentSdk != null) {
// manual unload?
sCurrentSdk = null;
}
final ArrayList<String> logMessages = new ArrayList<String>();
ISdkLog log = new ISdkLog() {
public void error(String errorFormat, Object... arg) {
logMessages.add(String.format(errorFormat, arg));
}
public void warning(String warningFormat, Object... arg) {
logMessages.add(String.format(warningFormat, arg));
}
};
// get an SdkManager object for the location
SdkManager manager = SdkManager.createManager(sdkLocation, log);
if (manager != null) {
sCurrentSdk = new Sdk(manager);
return sCurrentSdk;
} else {
StringBuilder sb = new StringBuilder("Error Loading the SDK:\n");
for (String msg : logMessages) {
sb.append('\n');
sb.append(msg);
}
AdtPlugin.displayError("Android SDK", sb.toString());
}
return null;
}
/**
* Returns the current {@link Sdk} object.
*/
public static Sdk getCurrent() {
return sCurrentSdk;
}
/**
* 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.
*/
public String getDocumentationBaseUrl() {
return mDocBaseUrl;
}
/**
* Returns the list of targets that are available in the SDK.
*/
public IAndroidTarget[] getTargets() {
return mManager.getTargets();
}
/**
* Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
* @param hash the hash
*/
public IAndroidTarget getTargetFromHashString(String hash) {
return mManager.getTargetFromHashString(hash);
}
/**
* Associates an {@link IProject} and an {@link IAndroidTarget}.
*/
public void setProject(IProject project, IAndroidTarget target) {
synchronized (mProjectMap) {
// look for the current target of the project
IAndroidTarget previousTarget = mProjectMap.get(project);
if (target != previousTarget) {
// save the target hash string in the project persistent property
setProjectTargetHashString(project, target.hashString());
// put it in a local map for easy access.
mProjectMap.put(project, target);
// recompile the project if needed.
IJavaProject javaProject = JavaCore.create(project);
AndroidClasspathContainerInitializer.updateProjects(
new IJavaProject[] { javaProject });
}
}
}
/**
* Returns the {@link IAndroidTarget} object associated with the given {@link IProject}.
*/
public IAndroidTarget getTarget(IProject project) {
synchronized (mProjectMap) {
IAndroidTarget target = mProjectMap.get(project);
if (target == null) {
// get the value from the project persistent property.
String targetHashString = getProjectTargetHashString(project);
if (targetHashString != null) {
target = mManager.getTargetFromHashString(targetHashString);
}
}
return target;
}
}
/**
* Returns the hash string uniquely identifying the target of a project. This methods reads
* the string from the project persistent preferences/properties.
* <p/>The string is equivalent to the return of {@link IAndroidTarget#hashString()}.
* @param project The project for which to return the target hash string.
* @return the hash string or null if the project does not have a target set.
*/
public static String getProjectTargetHashString(IProject project) {
// load the default.properties from the project folder.
ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString());
if (properties == null) {
AdtPlugin.log(IStatus.ERROR, "Failed to load properties file for project '%s'",
project.getName());
return null;
}
return properties.getProperty(ProjectProperties.PROPERTY_TARGET);
}
/**
* Sets a target hash string in a project's persistent preferences/property storage.
* @param project The project in which to save the hash string.
* @param targetHashString The target hash string to save. This must be the result from
* {@link IAndroidTarget#hashString()}.
*/
public static void setProjectTargetHashString(IProject project, String targetHashString) {
// because we don't want to erase other properties from default.properties, we first load
// them
ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString());
if (properties == null) {
// doesn't exist yet? we create it.
properties = ProjectProperties.create(project.getLocation().toOSString());
}
// add/change the target hash string.
properties.setProperty(ProjectProperties.PROPERTY_TARGET, targetHashString);
// and rewrite the file.
try {
properties.save();
} catch (IOException e) {
AdtPlugin.log(e, "Failed to save default.properties for project '%s'",
project.getName());
}
}
/**
* Return the {@link PlatformData} for a given {@link IAndroidTarget}.
*/
public AndroidTargetData getTargetData(IAndroidTarget target) {
synchronized (mTargetMap) {
return mTargetMap.get(target);
}
}
private Sdk(SdkManager manager) {
mManager = manager;
// pre-compute some paths
mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() +
SdkConstants.OS_SDK_DOCS_FOLDER);
}
void setTargetData(IAndroidTarget target, AndroidTargetData data) {
synchronized (mTargetMap) {
mTargetMap.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;
}
}

View File

@@ -14,9 +14,8 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.resources;
package com.android.ide.eclipse.adt.sdk;
import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass;
import com.android.ide.eclipse.common.AndroidConstants;
import org.eclipse.core.runtime.IProgressMonitor;
@@ -42,18 +41,19 @@ import javax.management.InvalidAttributeValueException;
* 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 WidgetListLoader implements IAndroidLoader {
public final class WidgetClassLoader implements IAndroidClassLoader {
/**
* Basic class containing the class descriptions found in the text file.
*/
private final static class ClassDescriptor implements IClass {
private final static class ClassDescriptor implements IClassDescriptor {
private String mName;
private String mSimpleName;
private ClassDescriptor mSuperClass;
private ClassDescriptor mEnclosingClass;
private final ArrayList<IClass> mDeclaredClasses = new ArrayList<IClass>();
private final ArrayList<IClassDescriptor> mDeclaredClasses =
new ArrayList<IClassDescriptor>();
private boolean mIsInstantiable = false;
ClassDescriptor(String fqcn) {
@@ -69,15 +69,15 @@ public final class WidgetListLoader implements IAndroidLoader {
return mSimpleName;
}
public IClass[] getDeclaredClasses() {
return mDeclaredClasses.toArray(new IClass[mDeclaredClasses.size()]);
public IClassDescriptor[] getDeclaredClasses() {
return mDeclaredClasses.toArray(new IClassDescriptor[mDeclaredClasses.size()]);
}
private void addDeclaredClass(ClassDescriptor declaredClass) {
mDeclaredClasses.add(declaredClass);
}
public IClass getEnclosingClass() {
public IClassDescriptor getEnclosingClass() {
return mEnclosingClass;
}
@@ -93,7 +93,7 @@ public final class WidgetListLoader implements IAndroidLoader {
mName = enclosingClass.mName + "$" + mName.substring(enclosingClass.mName.length() + 1);
}
public IClass getSuperclass() {
public IClassDescriptor getSuperclass() {
return mSuperClass;
}
@@ -147,7 +147,7 @@ public final class WidgetListLoader implements IAndroidLoader {
* @param osFilePath the OS path of the file to load.
* @throws FileNotFoundException if the file is not found.
*/
WidgetListLoader(String osFilePath) throws FileNotFoundException {
WidgetClassLoader(String osFilePath) throws FileNotFoundException {
mOsFilePath = osFilePath;
mReader = new BufferedReader(new FileReader(osFilePath));
}
@@ -301,20 +301,21 @@ public final class WidgetListLoader implements IAndroidLoader {
* @throws InvalidAttributeValueException
* @throws ClassFormatError
*/
public HashMap<String, ArrayList<IClass>> findClassesDerivingFrom(String rootPackage,
public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(String rootPackage,
String[] superClasses) throws IOException, InvalidAttributeValueException,
ClassFormatError {
HashMap<String, ArrayList<IClass>> map = new HashMap<String, ArrayList<IClass>>();
HashMap<String, ArrayList<IClassDescriptor>> map =
new HashMap<String, ArrayList<IClassDescriptor>>();
ArrayList<IClass> list = new ArrayList<IClass>();
ArrayList<IClassDescriptor> list = new ArrayList<IClassDescriptor>();
list.addAll(mWidgetMap.values());
map.put(AndroidConstants.CLASS_VIEW, list);
list = new ArrayList<IClass>();
list = new ArrayList<IClassDescriptor>();
list.addAll(mLayoutMap.values());
map.put(AndroidConstants.CLASS_VIEWGROUP, list);
list = new ArrayList<IClass>();
list = new ArrayList<IClassDescriptor>();
list.addAll(mLayoutParamsMap.values());
map.put(AndroidConstants.CLASS_VIEWGROUP_LAYOUTPARAMS, list);
@@ -326,7 +327,7 @@ public final class WidgetListLoader implements IAndroidLoader {
* @param className the fully-qualified name of the class to return.
* @throws ClassNotFoundException
*/
public IClass getClass(String className) throws ClassNotFoundException {
public IClassDescriptor getClass(String className) throws ClassNotFoundException {
return mMap.get(className);
}

View File

@@ -20,11 +20,13 @@
* org.eclipse.jdt.internal.ui.wizards.JavaProjectWizardFirstPage
*/
package com.android.ide.eclipse.adt.project.internal;
package com.android.ide.eclipse.adt.wizards.newproject;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestHelper;
import com.android.sdklib.IAndroidTarget;
import com.android.sdkuilib.SdkTargetSelector;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IProject;
@@ -120,6 +122,7 @@ public class NewProjectCreationPage extends WizardPage {
private boolean mInternalActivityNameUpdate;
protected boolean mProjectNameModifiedByUser;
protected boolean mApplicationNameModifiedByUser;
private SdkTargetSelector mSdkTargetSelector;
/**
@@ -133,7 +136,9 @@ public class NewProjectCreationPage extends WizardPage {
if (sCustomLocationOsPath == null ||
sCustomLocationOsPath.length() == 0 ||
!new File(sCustomLocationOsPath).isDirectory()) {
sCustomLocationOsPath = AdtPlugin.getOsSdkSamplesFolder();
// FIXME location of samples is pretty much impossible here.
//sCustomLocationOsPath = AdtPlugin.getOsSdkSamplesFolder();
sCustomLocationOsPath = File.listRoots()[0].getAbsolutePath();
}
}
@@ -200,6 +205,11 @@ public class NewProjectCreationPage extends WizardPage {
return mSourceFolder;
}
}
/** Returns the current sdk target or null if none has been selected yet. */
public IAndroidTarget getSdkTarget() {
return mSdkTargetSelector == null ? null : mSdkTargetSelector.getFirstSelected();
}
/**
* Overrides @DialogPage.setVisible(boolean) to put the focus in the project name when
@@ -232,15 +242,19 @@ public class NewProjectCreationPage extends WizardPage {
createProjectNameGroup(composite);
createLocationGroup(composite);
createTargetGroup(composite);
createPropertiesGroup(composite);
// Update state the first time
enableLocationWidgets();
setPageComplete(validatePage());
// Show description the first time
setErrorMessage(null);
setMessage(null);
setControl(composite);
// Validate. This will complain about the first empty field.
setPageComplete(validatePage());
}
/**
@@ -356,6 +370,33 @@ public class NewProjectCreationPage extends WizardPage {
});
}
/**
* Creates the target group.
* It only contains an SdkTargetSelector.
*/
private void createTargetGroup(Composite parent) {
Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
// Layout has 1 column
group.setLayout(new GridLayout());
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setFont(parent.getFont());
group.setText("Target");
// get the targets from the sdk
IAndroidTarget[] targets = null;
if (Sdk.getCurrent() != null) {
targets = Sdk.getCurrent().getTargets();
}
mSdkTargetSelector = new SdkTargetSelector(group, targets, false /*multi-selection*/);
mSdkTargetSelector.setSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
setPageComplete(validatePage());
}
});
}
/**
* Display a directory browser and update the location path field with the selected path
*/
@@ -754,6 +795,9 @@ public class NewProjectCreationPage extends WizardPage {
if ((status & MSG_ERROR) == 0) {
status |= validateLocationPath(workspace);
}
if ((status & MSG_ERROR) == 0) {
status |= validateSdkTarget();
}
if ((status & MSG_ERROR) == 0) {
status |= validatePackageField();
}
@@ -893,6 +937,18 @@ public class NewProjectCreationPage extends WizardPage {
return MSG_NONE;
}
/**
* Validates the sdk target choice.
*
* @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
*/
private int validateSdkTarget() {
if (getSdkTarget() == null) {
return setStatus("An SDK Target must be specified.", MSG_ERROR);
}
return MSG_NONE;
}
/**
* Validates the activity name field.
*

View File

@@ -14,12 +14,14 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.project.internal;
package com.android.ide.eclipse.adt.wizards.newproject;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.AndroidNature;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
@@ -60,6 +62,7 @@ import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
/**
* A "New Android Project" Wizard.
@@ -81,6 +84,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$
private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT"; //$NON-NLS-1$
private static final String PARAM_SRC_FOLDER = "SRC_FOLDER"; //$NON-NLS-1$
private static final String PARAM_SDK_TARGET = "SDK_TARGET"; //$NON-NLS-1$
private static final String PH_ACTIVITIES = "ACTIVITIES"; //$NON-NLS-1$
private static final String PH_INTENT_FILTERS = "INTENT_FILTERS"; //$NON-NLS-1$
@@ -223,13 +227,14 @@ public class NewProjectWizard extends Wizard implements INewWizard {
final IProject project = workspace.getRoot().getProject(mMainPage.getProjectName());
final IProjectDescription description = workspace.newProjectDescription(project.getName());
final Map<String, String> parameters = new HashMap<String, String>();
final Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put(PARAM_PROJECT, mMainPage.getProjectName());
parameters.put(PARAM_PACKAGE, mMainPage.getPackageName());
parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
parameters.put(PARAM_IS_NEW_PROJECT, Boolean.toString(mMainPage.isNewProject()));
parameters.put(PARAM_IS_NEW_PROJECT, mMainPage.isNewProject());
parameters.put(PARAM_SRC_FOLDER, mMainPage.getSourceFolder());
parameters.put(PARAM_SDK_TARGET, mMainPage.getSdkTarget());
if (mMainPage.isCreateActivity()) {
// An activity name can be of the form ".package.Class" or ".Class".
@@ -315,7 +320,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* to create or modify the project or if it is canceled by the user.
*/
private void createProjectAsync(IProject project, IProjectDescription description,
IProgressMonitor monitor, Map<String, String> parameters,
IProgressMonitor monitor, Map<String, Object> parameters,
Map<String, String> stringDictionary)
throws InvocationTargetException {
monitor.beginTask("Create Android Project", 100);
@@ -330,7 +335,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
// Create folders in the project if they don't already exist
addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
String[] sourceFolder = new String[] { parameters.get(PARAM_SRC_FOLDER) };
String[] sourceFolder = new String[] { (String) parameters.get(PARAM_SRC_FOLDER) };
addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolder, monitor);
// Create the resource folders in the project if they don't already exist.
@@ -340,7 +345,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
IJavaProject javaProject = JavaCore.create(project);
setupSourceFolder(javaProject, sourceFolder[0], monitor);
if (Boolean.parseBoolean(parameters.get(PARAM_IS_NEW_PROJECT))) {
if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
// Create files in the project if they don't already exist
addManifest(project, parameters, stringDictionary, monitor);
@@ -360,6 +365,8 @@ public class NewProjectWizard extends Wizard implements INewWizard {
monitor);
}
Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET));
// Fix the project to make sure all properties are as expected.
// Necessary for existing projects and good for new ones to.
ProjectHelper.fixProject(project);
@@ -409,7 +416,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* @throws IOException if the method fails to create the files in the
* project.
*/
private void addManifest(IProject project, Map<String, String> parameters,
private void addManifest(IProject project, Map<String, Object> parameters,
Map<String, String> stringDictionary, IProgressMonitor monitor)
throws CoreException, IOException {
@@ -543,16 +550,16 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* project.
*/
private void addSampleCode(IProject project, String sourceFolder,
Map<String, String> parameters, Map<String, String> stringDictionary,
Map<String, Object> parameters, Map<String, String> stringDictionary,
IProgressMonitor monitor) throws CoreException, IOException {
// create the java package directories.
IFolder pkgFolder = project.getFolder(sourceFolder);
String packageName = parameters.get(PARAM_PACKAGE);
String packageName = (String) parameters.get(PARAM_PACKAGE);
// The PARAM_ACTIVITY key will be absent if no activity should be created,
// in which case activityName will be null.
String activityName = parameters.get(PARAM_ACTIVITY);
Map<String, String> java_activity_parameters = parameters;
String activityName = (String) parameters.get(PARAM_ACTIVITY);
Map<String, Object> java_activity_parameters = parameters;
if (activityName != null) {
if (activityName.indexOf('.') >= 0) {
// There are package names in the activity name. Transform packageName to add
@@ -564,7 +571,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
// Also update the values used in the JAVA_FILE_TEMPLATE below
// (but not the ones from the manifest so don't change the caller's dictionary)
java_activity_parameters = new HashMap<String, String>(parameters);
java_activity_parameters = new HashMap<String, Object>(parameters);
java_activity_parameters.put(PARAM_PACKAGE, packageName);
java_activity_parameters.put(PARAM_ACTIVITY, activityName);
}
@@ -665,7 +672,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* length.
*/
private void copyFile(String resourceFilename, IFile destFile,
Map<String, String> parameters, IProgressMonitor monitor)
Map<String, Object> parameters, IProgressMonitor monitor)
throws CoreException, IOException {
// Read existing file.
@@ -692,13 +699,14 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* Replaces placeholders found in a string with values.
*
* @param str the string to search for placeholders.
* @param parameters a map of <placeholder, Value> to search for in the
* string
* @param parameters a map of <placeholder, Value> to search for in the string
* @return A new String object with the placeholder replaced by the values.
*/
private String replaceParameters(String str, Map<String, String> parameters) {
for (String key : parameters.keySet()) {
str = str.replaceAll(key, parameters.get(key));
private String replaceParameters(String str, Map<String, Object> parameters) {
for (Entry<String, Object> entry : parameters.entrySet()) {
if (entry.getValue() instanceof String) {
str = str.replaceAll(entry.getKey(), (String) entry.getValue());
}
}
return str;

View File

@@ -0,0 +1,287 @@
/*
* 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.common;
import com.android.sdklib.SdkConstants;
import java.io.File;
/**
* Constant definition class.<br>
* <br>
* Most constants have a prefix defining the content.
* <ul>
* <li><code>WS_</code> Workspace path constant. Those are absolute paths,
* from the project root.</li>
* <li><code>OS_</code> OS path constant. These paths are different depending on the platform.</li>
* <li><code>FN_</code> File name constant.</li>
* <li><code>FD_</code> Folder name constant.</li>
* <li><code>MARKER_</code> Resource Marker Ids constant.</li>
* <li><code>EXT_</code> File extension constant. This does NOT include a dot.</li>
* <li><code>DOT_</code> File extension constant. This start with a dot.</li>
* <li><code>RE_</code> Regexp constant.</li>
* <li><code>NS_</code> Namespace constant.</li>
* <li><code>CLASS_</code> Fully qualified class name.</li>
* </ul>
*
*/
public class AndroidConstants {
/**
* The old Editors Plugin ID. It is still used in some places for compatibility.
* Please do not use for new features.
*/
public static final String EDITORS_NAMESPACE = "com.android.ide.eclipse.editors"; // $NON-NLS-1$
/** Nature of android projects */
public final static String NATURE = "com.android.ide.eclipse.adt.AndroidNature"; //$NON-NLS-1$
public final static int PLATFORM_UNKNOWN = 0;
public final static int PLATFORM_LINUX = 1;
public final static int PLATFORM_WINDOWS = 2;
public final static int PLATFORM_DARWIN = 3;
/**
* Returns current platform, one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN},
* {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}.
*/
public final static int CURRENT_PLATFORM = currentPlatform();
/** Separator for workspace path, i.e. "/". */
public final static String WS_SEP = "/"; //$NON-NLS-1$
/** Separator character for workspace path, i.e. '/'. */
public final static char WS_SEP_CHAR = '/';
/** Extension of the Application package Files, i.e. "apk". */
public final static String EXT_ANDROID_PACKAGE = "apk"; //$NON-NLS-1$
/** Extension of java files, i.e. "java" */
public final static String EXT_JAVA = "java"; //$NON-NLS-1$
/** Extension of compiled java files, i.e. "class" */
public final static String EXT_CLASS = "class"; //$NON-NLS-1$
/** Extension of xml files, i.e. "xml" */
public final static String EXT_XML = "xml"; //$NON-NLS-1$
/** Extension of jar files, i.e. "jar" */
public final static String EXT_JAR = "jar"; //$NON-NLS-1$
/** Extension of aidl files, i.e. "aidl" */
public final static String EXT_AIDL = "aidl"; //$NON-NLS-1$
/** Extension of native libraries, i.e. "so" */
public final static String EXT_NATIVE_LIB = "so"; //$NON-NLS-1$
private final static String DOT = "."; //$NON-NLS-1$
/** Dot-Extension of the Application package Files, i.e. ".apk". */
public final static String DOT_ANDROID_PACKAGE = DOT + EXT_ANDROID_PACKAGE;
/** Dot-Extension of java files, i.e. ".java" */
public final static String DOT_JAVA = DOT + EXT_JAVA;
/** Dot-Extension of compiled java files, i.e. ".class" */
public final static String DOT_CLASS = DOT + EXT_CLASS;
/** Dot-Extension of xml files, i.e. ".xml" */
public final static String DOT_XML = DOT + EXT_XML;
/** Dot-Extension of jar files, i.e. ".jar" */
public final static String DOT_JAR = DOT + EXT_JAR;
/** Dot-Extension of aidl files, i.e. ".aidl" */
public final static String DOT_AIDL = DOT + EXT_AIDL;
/** Name of the manifest file, i.e. "AndroidManifest.xml". */
public static final String FN_ANDROID_MANIFEST = "AndroidManifest.xml"; //$NON-NLS-1$
public static final String FN_PROJECT_AIDL = "project.aidl"; //$NON-NLS-1$
/** dex.jar file */
public static final String FN_DX_JAR = "dx.jar"; //$NON-NLS-1$
/** Name of the android sources directory */
public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$
/** Resource java class filename, i.e. "R.java" */
public final static String FN_RESOURCE_CLASS = "R.java"; //$NON-NLS-1$
/** Resource class file filename, i.e. "R.class" */
public final static String FN_COMPILED_RESOURCE_CLASS = "R.class"; //$NON-NLS-1$
/** Manifest java class filename, i.e. "Manifest.java" */
public final static String FN_MANIFEST_CLASS = "Manifest.java"; //$NON-NLS-1$
/** Dex conversion output filname, i.e. "classes.dex" */
public final static String FN_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$
/** Temporary packaged resources file name, i.e. "resources.ap_" */
public final static String FN_RESOURCES_AP_ = "resources.ap_"; //$NON-NLS-1$
public final static String FN_ADB = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
"adb.exe" : "adb"; //$NON-NLS-1$ //$NON-NLS-2$
public final static String FN_AAPT = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
"aapt.exe" : "aapt"; //$NON-NLS-1$ //$NON-NLS-2$
public final static String FN_AIDL = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
"aidl.exe" : "aidl"; //$NON-NLS-1$ //$NON-NLS-2$
public final static String FN_EMULATOR = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
"emulator.exe" : "emulator"; //$NON-NLS-1$ //$NON-NLS-2$
public final static String FN_TRACEVIEW = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
"traceview.exe" : "traceview"; //$NON-NLS-1$ //$NON-NLS-2$
/** Folder Names for Android Projects . */
/* Resources folder name, i.e. "res". */
public final static String FD_RESOURCES = "res"; //$NON-NLS-1$
/** Assets folder name, i.e. "assets" */
public final static String FD_ASSETS = "assets"; //$NON-NLS-1$
/** Default source folder name, i.e. "src" */
public final static String FD_SOURCES = "src"; //$NON-NLS-1$
/** Default native library folder name inside the project, i.e. "libs"
* While the folder inside the .apk is "lib", we call that one libs because
* that's what we use in ant for both .jar and .so and we need to make the 2 development ways
* compatible. */
public final static String FD_NATIVE_LIBS = "libs"; //$NON-NLS-1$
/** Native lib folder inside the APK: "lib" */
public final static String FD_APK_NATIVE_LIBS = "lib"; //$NON-NLS-1$
/** Default bin folder name, i.e. "bin" */
public final static String FD_BINARIES = "bin"; //$NON-NLS-1$
/** Default anim resource folder name, i.e. "anim" */
public final static String FD_ANIM = "anim"; //$NON-NLS-1$
/** Default color resource folder name, i.e. "color" */
public final static String FD_COLOR = "color"; //$NON-NLS-1$
/** Default drawable resource folder name, i.e. "drawable" */
public final static String FD_DRAWABLE = "drawable"; //$NON-NLS-1$
/** Default layout resource folder name, i.e. "layout" */
public final static String FD_LAYOUT = "layout"; //$NON-NLS-1$
/** Default menu resource folder name, i.e. "menu" */
public final static String FD_MENU = "menu"; //$NON-NLS-1$
/** Default values resource folder name, i.e. "values" */
public final static String FD_VALUES = "values"; //$NON-NLS-1$
/** Default xml resource folder name, i.e. "xml" */
public final static String FD_XML = "xml"; //$NON-NLS-1$
/** Default raw resource folder name, i.e. "raw" */
public final static String FD_RAW = "raw"; //$NON-NLS-1$
/** Absolute path of the workspace root, i.e. "/" */
public final static String WS_ROOT = WS_SEP;
/** Absolute path of the resource folder, eg "/res".<br> This is a workspace path. */
public final static String WS_RESOURCES = WS_SEP + FD_RESOURCES;
/** Absolute path of the resource folder, eg "/assets".<br> This is a workspace path. */
public final static String WS_ASSETS = WS_SEP + FD_ASSETS;
/** Leaf of the javaDoc folder. Does not start with a separator. */
public final static String WS_JAVADOC_FOLDER_LEAF = SdkConstants.FD_DOCS + "/reference"; //$NON-NLS-1$
/** Path of the samples directory relative to the sdk folder.
* This is an OS path, ending with a separator.
* FIXME: remove once the NPW is fixed. */
public final static String OS_SDK_SAMPLES_FOLDER = SdkConstants.FD_SAMPLES + File.separator;
/** Path of the dx.jar file relative to the sdk folder. */
public final static String OS_SDK_LIBS_DX_JAR =
SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + FN_DX_JAR;
/** Regexp for single dot */
public final static String RE_DOT = "\\."; //$NON-NLS-1$
/** Regexp for java extension, i.e. "\.java$" */
public final static String RE_JAVA_EXT = "\\.java$"; //$NON-NLS-1$
/** Regexp for aidl extension, i.e. "\.aidl$" */
public final static String RE_AIDL_EXT = "\\.aidl$"; //$NON-NLS-1$
/** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */
public final static String NS_RESOURCES = "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$
/** Namespace pattern for the custom resource XML, i.e. "http://schemas.android.com/apk/res/%s" */
public final static String NS_CUSTOM_RESOURCES = "http://schemas.android.com/apk/res/%1$s"; //$NON-NLS-1$
/** The old common plug-in ID. Please do not use for new features. */
public static final String COMMON_PLUGIN_ID = "com.android.ide.eclipse.common"; //$NON-NLS-1$
/** aapt marker error. */
public final static String MARKER_AAPT = COMMON_PLUGIN_ID + ".aaptProblem"; //$NON-NLS-1$
/** XML marker error. */
public final static String MARKER_XML = COMMON_PLUGIN_ID + ".xmlProblem"; //$NON-NLS-1$
/** aidl marker error. */
public final static String MARKER_AIDL = COMMON_PLUGIN_ID + ".aidlProblem"; //$NON-NLS-1$
/** android marker error */
public final static String MARKER_ANDROID = COMMON_PLUGIN_ID + ".androidProblem"; //$NON-NLS-1$
/** Name for the "type" marker attribute */
public final static String MARKER_ATTR_TYPE = "android.type"; //$NON-NLS-1$
/** Name for the "class" marker attribute */
public final static String MARKER_ATTR_CLASS = "android.class"; //$NON-NLS-1$
/** activity value for marker attribute "type" */
public final static String MARKER_ATTR_TYPE_ACTIVITY = "activity"; //$NON-NLS-1$
/** service value for marker attribute "type" */
public final static String MARKER_ATTR_TYPE_SERVICE = "service"; //$NON-NLS-1$
/** receiver value for marker attribute "type" */
public final static String MARKER_ATTR_TYPE_RECEIVER = "receiver"; //$NON-NLS-1$
/** provider value for marker attribute "type" */
public final static String MARKER_ATTR_TYPE_PROVIDER = "provider"; //$NON-NLS-1$
public final static String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$
public final static String CLASS_SERVICE = "android.app.Service"; //$NON-NLS-1$
public final static String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$
public final static String CLASS_CONTENTPROVIDER = "android.content.ContentProvider"; //$NON-NLS-1$
public final static String CLASS_INSTRUMENTATION = "android.app.Instrumentation"; //$NON-NLS-1$
public final static String CLASS_BUNDLE = "android.os.Bundle"; //$NON-NLS-1$
public final static String CLASS_R = "android.R"; //$NON-NLS-1$
public final static String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$
public final static String CLASS_INTENT = "android.content.Intent"; //$NON-NLS-1$
public final static String CLASS_CONTEXT = "android.content.Context"; //$NON-NLS-1$
public final static String CLASS_VIEW = "android.view.View"; //$NON-NLS-1$
public final static String CLASS_VIEWGROUP = "android.view.ViewGroup"; //$NON-NLS-1$
public final static String CLASS_LAYOUTPARAMS = "LayoutParams"; //$NON-NLS-1$
public final static String CLASS_VIEWGROUP_LAYOUTPARAMS =
CLASS_VIEWGROUP + "$" + CLASS_LAYOUTPARAMS; //$NON-NLS-1$
public final static String CLASS_FRAMELAYOUT = "FrameLayout"; //$NON-NLS-1$
public final static String CLASS_PREFERENCE = "android.preference.Preference"; //$NON-NLS-1$
public final static String CLASS_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$
public final static String CLASS_PREFERENCES =
"android.preference." + CLASS_PREFERENCE_SCREEN; //$NON-NLS-1$
public final static String CLASS_PREFERENCEGROUP = "android.preference.PreferenceGroup"; //$NON-NLS-1$
public final static String CLASS_PARCELABLE = "android.os.Parcelable"; //$NON-NLS-1$
public final static String CLASS_BRIDGE = "com.android.layoutlib.bridge.Bridge"; //$NON-NLS-1$
/**
* Prefered compiler level, i.e. "1.5".
*/
public final static String COMPILER_COMPLIANCE_PREFERRED = "1.5"; //$NON-NLS-1$
/**
* List of valid compiler level, i.e. "1.5" and "1.6"
*/
public final static String[] COMPILER_COMPLIANCE = {
"1.5", //$NON-NLS-1$
"1.6", //$NON-NLS-1$
};
/** The base URL where to find the Android class & manifest documentation */
public static final String CODESITE_BASE_URL = "http://code.google.com/android"; //$NON-NLS-1$
/**
* Returns current platform
*
* @return one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN},
* {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}.
*/
private static int currentPlatform() {
String os = System.getProperty("os.name"); //$NON-NLS-1$
if (os.startsWith("Mac OS")) { //$NON-NLS-1$
return PLATFORM_DARWIN;
} else if (os.startsWith("Windows")) { //$NON-NLS-1$
return PLATFORM_WINDOWS;
} else if (os.startsWith("Linux")) { //$NON-NLS-1$
return PLATFORM_LINUX;
}
return PLATFORM_UNKNOWN;
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.common;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
/**
* Helpers for Eclipse UI related stuff.
*/
public final class EclipseUiHelper {
/** View Id for the default Eclipse Content Outline view. */
public static final String CONTENT_OUTLINE_VIEW_ID = "org.eclipse.ui.views.ContentOutline";
/** View Id for the default Eclipse Property Sheet view. */
public static final String PROPERTY_SHEET_VIEW_ID = "org.eclipse.ui.views.PropertySheet";
/** This class never gets instantiated. */
private EclipseUiHelper() {
}
/**
* Shows the corresponding view.
* <p/>
* Silently fails in case of error.
*
* @param viewId One of {@link #CONTENT_OUTLINE_VIEW_ID}, {@link #PROPERTY_SHEET_VIEW_ID}.
* @param activate True to force activate (i.e. takes focus), false to just make visible (i.e.
* does not steal focus.)
*/
public static void showView(String viewId, boolean activate) {
IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (win != null) {
IWorkbenchPage page = win.getActivePage();
if (page != null) {
try {
IViewPart part = page.showView(viewId,
null /* secondaryId */,
activate ? IWorkbenchPage.VIEW_ACTIVATE : IWorkbenchPage.VIEW_VISIBLE);
} catch (PartInitException e) {
// ignore
}
}
}
}
}

View File

@@ -0,0 +1,21 @@
package com.android.ide.eclipse.common;
import org.eclipse.osgi.util.NLS;
public class Messages extends NLS {
private static final String BUNDLE_NAME = "com.android.ide.eclipse.common.messages"; //$NON-NLS-1$
public static String Console_Data_Project_Tag;
public static String Console_Date_Tag;
static {
// initialize resource bundle
NLS.initializeMessages(BUNDLE_NAME, Messages.class);
}
private Messages() {
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.common;
import com.android.sdkstats.SdkStatsService;
import org.osgi.framework.Version;
/**
* Helper class to access the ping usage stat server.
*/
public class SdkStatsHelper {
/**
* Pings the usage start server.
* @param pluginName the name of the plugin to appear in the stats
* @param pluginVersion the {@link Version} of the plugin.
*/
public static void pingUsageServer(String pluginName, Version pluginVersion) {
String versionString = String.format("%1$d.%2$d.%3$d", pluginVersion.getMajor(),
pluginVersion.getMinor(), pluginVersion.getMicro());
SdkStatsService.ping(pluginName, versionString);
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.common;
import org.eclipse.ui.console.MessageConsoleStream;
import java.util.Calendar;
/**
* Stream helper class.
*/
public class StreamHelper {
/**
* Prints messages, associated with a project to the specified stream
* @param stream The stream to write to
* @param tag The tag associated to the message. Can be null
* @param objects The objects to print through their toString() method (or directly for
* {@link String} objects.
*/
public static synchronized void printToStream(MessageConsoleStream stream, String tag,
Object... objects) {
String dateTag = getMessageTag(tag);
for (Object obj : objects) {
stream.print(dateTag);
if (obj instanceof String) {
stream.println((String)obj);
} else {
stream.println(obj.toString());
}
}
}
/**
* Creates a string containing the current date/time, and the tag
* @param tag The tag associated to the message. Can be null
* @return The dateTag
*/
public static String getMessageTag(String tag) {
Calendar c = Calendar.getInstance();
if (tag == null) {
return String.format(Messages.Console_Date_Tag, c);
}
return String.format(Messages.Console_Data_Project_Tag, c, tag);
}
}

View File

@@ -0,0 +1,2 @@
Console_Date_Tag=[%1$tF %1$tT]
Console_Data_Project_Tag=[%1$tF %1$tT - %2$s]

View File

@@ -0,0 +1,123 @@
/*
* 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.common.preferences;
import com.android.sdkstats.SdkStatsService;
import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.preference.PreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Link;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import java.io.IOException;
public class UsagePreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
private BooleanFieldEditor mOptInCheckBox;
public UsagePreferencePage() {
}
public void init(IWorkbench workbench) {
// pass
}
@Override
protected Control createContents(Composite parent) {
Composite top = new Composite(parent, SWT.NONE);
top.setLayout(new GridLayout(1, false));
top.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
Link text = new Link(top, SWT.WRAP);
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
gd.widthHint = 200;
text.setLayoutData(gd);
text.setText(SdkStatsService.BODY_TEXT);
text.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
SdkStatsService.openUrl(event.text);
}
});
mOptInCheckBox = new BooleanFieldEditor(SdkStatsService.PING_OPT_IN,
SdkStatsService.CHECKBOX_TEXT, top);
mOptInCheckBox.setPage(this);
mOptInCheckBox.setPreferenceStore(SdkStatsService.getPreferenceStore());
mOptInCheckBox.load();
return top;
}
/* (non-Javadoc)
* @see org.eclipse.jface.preference.PreferencePage#performCancel()
*/
@Override
public boolean performCancel() {
mOptInCheckBox.load();
return super.performCancel();
}
/* (non-Javadoc)
* @see org.eclipse.jface.preference.PreferencePage#performDefaults()
*/
@Override
protected void performDefaults() {
mOptInCheckBox.loadDefault();
super.performDefaults();
}
/* (non-Javadoc)
* @see org.eclipse.jface.preference.PreferencePage#performOk()
*/
@Override
public boolean performOk() {
save();
return super.performOk();
}
/* (non-Javadoc)
* @see org.eclipse.jface.preference.PreferencePage#performApply()
*/
@Override
protected void performApply() {
save();
super.performApply();
}
private void save() {
try {
PreferenceStore store = SdkStatsService.getPreferenceStore();
if (store != null) {
store.setValue(SdkStatsService.PING_OPT_IN, mOptInCheckBox.getBooleanValue());
store.save();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,241 @@
/*
* 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.common.project;
import com.android.ide.eclipse.common.AndroidConstants;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.xml.sax.InputSource;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
/**
* Utility class that manages the AndroidManifest.xml file.
* <p/>
* All the get method work by XPath. Repeated calls to those may warrant using
* {@link AndroidManifestParser} instead.
*/
public class AndroidManifestHelper {
private IFile mManifestIFile;
private File mManifestFile;
private XPath mXPath;
/**
* Creates an AndroidManifest based on an existing Eclipse {@link IProject} object.
* </p>
* Use {@link #exists()} to check if the manifest file really exists in the project.
*
* @param project The project to search for the manifest.
*/
public AndroidManifestHelper(IProject project) {
mXPath = AndroidXPathFactory.newXPath();
mManifestIFile = getManifest(project);
}
/**
* Creates an AndroidManifest based on a file path.
* <p/>
* Use {@link #exists()} to check if the manifest file really exists.
*
* @param osManifestFilePath the os path to the AndroidManifest.xml file.
*/
public AndroidManifestHelper(String osManifestFilePath) {
mXPath = AndroidXPathFactory.newXPath();
mManifestFile = new File(osManifestFilePath);
}
/**
* Returns the underlying {@link IFile} for the android manifest XML file, if found in the
* given Eclipse project.
*
* Always return null if the constructor that takes an {@link IProject} was NOT called.
*
* @return The IFile for the androidManifest.xml or null if no such file could be found.
*/
public IFile getManifestIFile() {
return mManifestIFile;
}
/**
* Returns the underlying {@link File} for the android manifest XML file.
*/
public File getManifestFile() {
if (mManifestIFile != null) {
return mManifestIFile.getLocation().toFile();
}
return mManifestFile;
}
/**
* Returns the package name defined in the manifest file.
*
* @return A String object with the package or null if any error happened.
*/
public String getPackageName() {
try {
return getPackageNameInternal(mXPath, getSource());
} catch (XPathExpressionException e1) {
// If the XPath failed to evaluate, we'll return null.
} catch (Exception e) {
// if this happens this is due to the resource being out of sync.
// so we must refresh it and do it again
// for any other kind of exception we must return null as well;
}
return null;
}
/**
* Returns the i-th activity defined in the manifest file.
*
* @param manifest The manifest's IFile object.
* @param index The 1-based index of the activity to return.
* @param xpath An optional xpath object. If null is provided a new one will
* be created.
* @return A String object with the activity or null if any error happened.
*/
public String getActivityName(int index) {
try {
return getActivityNameInternal(index, mXPath, getSource());
} catch (XPathExpressionException e1) {
// If the XPath failed to evaluate, we'll return null.
} catch (Exception e) {
// if this happens this is due to the resource being out of sync.
// so we must refresh it and do it again
// for any other kind of exception we must return null as well;
}
return null;
}
/**
* Returns an IFile object representing the manifest for the specified
* project.
*
* @param project The project containing the manifest file.
* @return An IFile object pointing to the manifest or null if the manifest
* is missing.
*/
public static IFile getManifest(IProject project) {
IResource r = project.findMember(AndroidConstants.WS_SEP
+ AndroidConstants.FN_ANDROID_MANIFEST);
if (r == null || r.exists() == false || (r instanceof IFile) == false) {
return null;
}
return (IFile) r;
}
/**
* Combines a java package, with a class value from the manifest to make a fully qualified
* class name
* @param javaPackage the java package from the manifest.
* @param className the class name from the manifest.
* @return the fully qualified class name.
*/
public static String combinePackageAndClassName(String javaPackage, String className) {
if (className == null || className.length() == 0) {
return javaPackage;
}
if (javaPackage == null || javaPackage.length() == 0) {
return className;
}
// the class name can be a subpackage (starts with a '.'
// char), a simple class name (no dot), or a full java package
boolean startWithDot = (className.charAt(0) == '.');
boolean hasDot = (className.indexOf('.') != -1);
if (startWithDot || hasDot == false) {
// add the concatenation of the package and class name
if (startWithDot) {
return javaPackage + className;
} else {
return javaPackage + '.' + className;
}
} else {
// just add the class as it should be a fully qualified java name.
return className;
}
}
/**
* Returns true either if an androidManifest.xml file was found in the project
* or if the given file path exists.
*/
public boolean exists() {
if (mManifestIFile != null) {
return mManifestIFile.exists();
} else if (mManifestFile != null) {
return mManifestFile.exists();
}
return false;
}
/**
* Returns an InputSource for XPath.
*
* @throws FileNotFoundException if file does not exist.
* @throws CoreException if the {@link IFile} does not exist.
*/
private InputSource getSource() throws FileNotFoundException, CoreException {
if (mManifestIFile != null) {
return new InputSource(mManifestIFile.getContents());
} else if (mManifestFile != null) {
return new InputSource(new FileReader(mManifestFile));
}
return null;
}
/**
* Performs the actual XPath evaluation to get the package name.
* Extracted so that we can share it with AndroidManifestFromProject.
*/
private static String getPackageNameInternal(XPath xpath, InputSource source)
throws XPathExpressionException {
return xpath.evaluate("/manifest/@package", source); //$NON-NLS-1$
}
/**
* Performs the actual XPath evaluation to get the activity name.
* Extracted so that we can share it with AndroidManifestFromProject.
*/
private static String getActivityNameInternal(int index, XPath xpath, InputSource source)
throws XPathExpressionException {
return xpath.evaluate("/manifest/application/activity[" //$NON-NLS-1$
+ index
+ "]/@" //$NON-NLS-1$
+ AndroidXPathFactory.DEFAULT_NS_PREFIX +":name", //$NON-NLS-1$
source);
}
}

View File

@@ -0,0 +1,666 @@
/*
* 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.common.project;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaProject;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
public class AndroidManifestParser {
private final static String ATTRIBUTE_PACKAGE = "package"; //$NON-NLS-1$
private final static String ATTRIBUTE_NAME = "name"; //$NON-NLS-1$
private final static String ATTRIBUTE_PROCESS = "process"; //$NON-NLS-$
private final static String ATTRIBUTE_DEBUGGABLE = "debuggable"; //$NON-NLS-$
private final static String ATTRIBUTE_MIN_SDK_VERSION = "minSdkVersion"; //$NON-NLS-$
private final static String NODE_MANIFEST = "manifest"; //$NON-NLS-1$
private final static String NODE_APPLICATION = "application"; //$NON-NLS-1$
private final static String NODE_ACTIVITY = "activity"; //$NON-NLS-1$
private final static String NODE_SERVICE = "service"; //$NON-NLS-1$
private final static String NODE_RECEIVER = "receiver"; //$NON-NLS-1$
private final static String NODE_PROVIDER = "provider"; //$NON-NLS-1$
private final static String NODE_INTENT = "intent-filter"; //$NON-NLS-1$
private final static String NODE_ACTION = "action"; //$NON-NLS-1$
private final static String NODE_CATEGORY = "category"; //$NON-NLS-1$
private final static String NODE_USES_SDK = "uses-sdk"; //$NON-NLS-1$
private final static int LEVEL_MANIFEST = 0;
private final static int LEVEL_APPLICATION = 1;
private final static int LEVEL_ACTIVITY = 2;
private final static int LEVEL_INTENT_FILTER = 3;
private final static int LEVEL_CATEGORY = 4;
private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$
private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$
private static class ManifestHandler extends XmlErrorHandler {
//--- data read from the parsing
/** Application package */
private String mPackage;
/** List of all activities */
private final ArrayList<String> mActivities = new ArrayList<String>();
/** Launcher activity */
private String mLauncherActivity = null;
/** list of process names declared by the manifest */
private Set<String> mProcesses = null;
/** debuggable attribute value. If null, the attribute is not present. */
private Boolean mDebuggable = null;
/** API level requirement. if 0 the attribute was not present. */
private int mApiLevelRequirement = 0;
//--- temporary data/flags used during parsing
private IJavaProject mJavaProject;
private boolean mGatherData = false;
private boolean mMarkErrors = false;
private int mCurrentLevel = 0;
private int mValidLevel = 0;
private boolean mFoundMainAction = false;
private boolean mFoundLauncherCategory = false;
private String mCurrentActivity = null;
private Locator mLocator;
/**
*
* @param manifestFile
* @param errorListener
* @param gatherData
* @param javaProject
* @param markErrors
*/
ManifestHandler(IFile manifestFile, XmlErrorListener errorListener,
boolean gatherData, IJavaProject javaProject, boolean markErrors) {
super(manifestFile, errorListener);
mGatherData = gatherData;
mJavaProject = javaProject;
mMarkErrors = markErrors;
}
/**
* Returns the package defined in the manifest, if found.
* @return The package name or null if not found.
*/
String getPackage() {
return mPackage;
}
/**
* Returns the list of activities found in the manifest.
* @return An array of fully qualified class names, or empty if no activity were found.
*/
String[] getActivities() {
return mActivities.toArray(new String[mActivities.size()]);
}
/**
* Returns the name of one activity found in the manifest, that is configured to show
* up in the HOME screen.
* @return the fully qualified name of a HOME activity or null if none were found.
*/
String getLauncherActivity() {
return mLauncherActivity;
}
/**
* Returns the list of process names declared by the manifest.
*/
String[] getProcesses() {
if (mProcesses != null) {
return mProcesses.toArray(new String[mProcesses.size()]);
}
return new String[0];
}
/**
* Returns the <code>debuggable</code> attribute value or null if it is not set.
*/
Boolean getDebuggable() {
return mDebuggable;
}
/**
* Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set.
*/
int getApiLevelRequirement() {
return mApiLevelRequirement;
}
/* (non-Javadoc)
* @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator)
*/
@Override
public void setDocumentLocator(Locator locator) {
mLocator = locator;
super.setDocumentLocator(locator);
}
/* (non-Javadoc)
* @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
* java.lang.String, org.xml.sax.Attributes)
*/
@Override
public void startElement(String uri, String localName, String name, Attributes attributes)
throws SAXException {
try {
if (mGatherData == false) {
return;
}
// if we're at a valid level
if (mValidLevel == mCurrentLevel) {
String value;
switch (mValidLevel) {
case LEVEL_MANIFEST:
if (NODE_MANIFEST.equals(localName)) {
// lets get the package name.
mPackage = getAttributeValue(attributes, ATTRIBUTE_PACKAGE,
false /* hasNamespace */);
mValidLevel++;
}
break;
case LEVEL_APPLICATION:
if (NODE_APPLICATION.equals(localName)) {
value = getAttributeValue(attributes, ATTRIBUTE_PROCESS,
true /* hasNamespace */);
if (value != null) {
addProcessName(value);
}
value = getAttributeValue(attributes, ATTRIBUTE_DEBUGGABLE,
true /* hasNamespace*/);
if (value != null) {
mDebuggable = Boolean.parseBoolean(value);
}
mValidLevel++;
} else if (NODE_USES_SDK.equals(localName)) {
value = getAttributeValue(attributes, ATTRIBUTE_MIN_SDK_VERSION,
true /* hasNamespace */);
try {
mApiLevelRequirement = Integer.parseInt(value);
} catch (NumberFormatException e) {
handleError(e, -1 /* lineNumber */);
}
}
break;
case LEVEL_ACTIVITY:
if (NODE_ACTIVITY.equals(localName)) {
processActivityNode(attributes);
mValidLevel++;
} else if (NODE_SERVICE.equals(localName)) {
processNode(attributes, AndroidConstants.CLASS_SERVICE);
mValidLevel++;
} else if (NODE_RECEIVER.equals(localName)) {
processNode(attributes, AndroidConstants.CLASS_BROADCASTRECEIVER);
mValidLevel++;
} else if (NODE_PROVIDER.equals(localName)) {
processNode(attributes, AndroidConstants.CLASS_CONTENTPROVIDER);
mValidLevel++;
}
break;
case LEVEL_INTENT_FILTER:
// only process this level if we are in an activity
if (mCurrentActivity != null && NODE_INTENT.equals(localName)) {
// if we're at the intent level, lets reset some flag to
// be used when parsing the children
mFoundMainAction = false;
mFoundLauncherCategory = false;
mValidLevel++;
}
break;
case LEVEL_CATEGORY:
if (mCurrentActivity != null && mLauncherActivity == null) {
if (NODE_ACTION.equals(localName)) {
// get the name attribute
if (ACTION_MAIN.equals(
getAttributeValue(attributes, ATTRIBUTE_NAME,
true /* hasNamespace */))) {
mFoundMainAction = true;
}
} else if (NODE_CATEGORY.equals(localName)) {
if (CATEGORY_LAUNCHER.equals(
getAttributeValue(attributes, ATTRIBUTE_NAME,
true /* hasNamespace */))) {
mFoundLauncherCategory = true;
}
}
// no need to increase mValidLevel as we don't process anything
// below this level.
}
break;
}
}
mCurrentLevel++;
} finally {
super.startElement(uri, localName, name, attributes);
}
}
/* (non-Javadoc)
* @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
* java.lang.String)
*/
@Override
public void endElement(String uri, String localName, String name) throws SAXException {
try {
if (mGatherData == false) {
return;
}
// decrement the levels.
if (mValidLevel == mCurrentLevel) {
mValidLevel--;
}
mCurrentLevel--;
// if we're at a valid level
// process the end of the element
if (mValidLevel == mCurrentLevel) {
switch (mValidLevel) {
case LEVEL_ACTIVITY:
mCurrentActivity = null;
break;
case LEVEL_INTENT_FILTER:
// if we found both a main action and a launcher category, this is our
// launcher activity!
if (mCurrentActivity != null &&
mFoundMainAction && mFoundLauncherCategory) {
mLauncherActivity = mCurrentActivity;
}
break;
default:
break;
}
}
} finally {
super.endElement(uri, localName, name);
}
}
/* (non-Javadoc)
* @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException)
*/
@Override
public void error(SAXParseException e) throws SAXException {
if (mMarkErrors) {
handleError(e, e.getLineNumber());
}
}
/* (non-Javadoc)
* @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException)
*/
@Override
public void fatalError(SAXParseException e) throws SAXException {
if (mMarkErrors) {
handleError(e, e.getLineNumber());
}
}
/* (non-Javadoc)
* @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException)
*/
@Override
public void warning(SAXParseException e) throws SAXException {
if (mMarkErrors) {
super.warning(e);
}
}
/**
* Processes the activity node.
* @param attributes the attributes for the activity node.
* @throws CoreException
*/
private void processActivityNode(Attributes attributes) {
// lets get the activity name, and add it to the list
String activityName = getAttributeValue(attributes, ATTRIBUTE_NAME,
true /* hasNamespace */);
if (activityName != null) {
mCurrentActivity = AndroidManifestHelper.combinePackageAndClassName(mPackage,
activityName);
mActivities.add(mCurrentActivity);
if (mMarkErrors) {
checkClass(mCurrentActivity, AndroidConstants.CLASS_ACTIVITY,
true /* testVisibility */);
}
} else {
// no activity found! Aapt will output an error,
// so we don't have to do anything
mCurrentActivity = activityName;
}
String processName = getAttributeValue(attributes, ATTRIBUTE_PROCESS,
true /* hasNamespace */);
if (processName != null) {
addProcessName(processName);
}
}
/**
* Processes the service/receiver/provider nodes.
* @param attributes the attributes for the activity node.
* @param superClassName the fully qualified name of the super class that this
* node is representing
* @throws CoreException
*/
private void processNode(Attributes attributes, String superClassName) {
// lets get the class name, and check it if required.
String serviceName = getAttributeValue(attributes, ATTRIBUTE_NAME,
true /* hasNamespace */);
if (serviceName != null) {
serviceName = AndroidManifestHelper.combinePackageAndClassName(mPackage,
serviceName);
if (mMarkErrors) {
checkClass(serviceName, superClassName, false /* testVisibility */);
}
}
String processName = getAttributeValue(attributes, ATTRIBUTE_PROCESS,
true /* hasNamespace */);
if (processName != null) {
addProcessName(processName);
}
}
/**
* Checks that a class is valid and can be used in the Android Manifest.
* <p/>
* Errors are put as {@link IMarker} on the manifest file.
* @param className the fully qualified name of the class to test.
* @param superClassName the fully qualified name of the class it is supposed to extend.
* @param testVisibility if <code>true</code>, the method will check the visibility of
* the class or of its constructors.
*/
private void checkClass(String className, String superClassName, boolean testVisibility) {
// we need to check the validity of the activity.
String result = BaseProjectHelper.testClassForManifest(mJavaProject,
className, superClassName, testVisibility);
if (result != BaseProjectHelper.TEST_CLASS_OK) {
// get the line number
int line = mLocator.getLineNumber();
// mark the file
IMarker marker = BaseProjectHelper.addMarker(getFile(),
AndroidConstants.MARKER_ANDROID,
result, line, IMarker.SEVERITY_ERROR);
// add custom attributes to be used by the manifest editor.
if (marker != null) {
try {
marker.setAttribute(AndroidConstants.MARKER_ATTR_TYPE,
AndroidConstants.MARKER_ATTR_TYPE_ACTIVITY);
marker.setAttribute(AndroidConstants.MARKER_ATTR_CLASS, className);
} catch (CoreException e) {
}
}
}
}
/**
* Searches through the attributes list for a particular one and returns its value.
* @param attributes the attribute list to search through
* @param attributeName the name of the attribute to look for.
* @param hasNamespace Indicates whether the attribute has an android namespace.
* @return a String with the value or null if the attribute was not found.
* @see AndroidConstants#NS_RESOURCES
*/
private String getAttributeValue(Attributes attributes, String attributeName,
boolean hasNamespace) {
int count = attributes.getLength();
for (int i = 0 ; i < count ; i++) {
if (attributeName.equals(attributes.getLocalName(i)) &&
((hasNamespace &&
AndroidConstants.NS_RESOURCES.equals(attributes.getURI(i))) ||
(hasNamespace == false && attributes.getURI(i).length() == 0))) {
return attributes.getValue(i);
}
}
return null;
}
private void addProcessName(String processName) {
if (mProcesses == null) {
mProcesses = new TreeSet<String>();
}
mProcesses.add(processName);
}
}
private static SAXParserFactory sParserFactory;
private final String mJavaPackage;
private final String[] mActivities;
private final String mLauncherActivity;
private final String[] mProcesses;
private final Boolean mDebuggable;
private final int mApiLevelRequirement;
static {
sParserFactory = SAXParserFactory.newInstance();
sParserFactory.setNamespaceAware(true);
}
/**
* Parses the Android Manifest, and returns an object containing
* the result of the parsing.
* @param javaProject The java project.
* @param manifestFile the {@link IFile} representing the manifest file.
* @param errorListener
* @param gatherData indicates whether the parsing will extract data from the manifest.
* @param markErrors indicates whether the error found during parsing should put a
* marker on the file. For class validation errors to put a marker, <code>gatherData</code>
* must be set to <code>true</code>
* @return an {@link AndroidManifestParser} or null if the parsing failed.
* @throws CoreException
*/
public static AndroidManifestParser parse(IJavaProject javaProject, IFile manifestFile,
XmlErrorListener errorListener, boolean gatherData, boolean markErrors)
throws CoreException {
try {
SAXParser parser = sParserFactory.newSAXParser();
ManifestHandler manifestHandler = new ManifestHandler(manifestFile,
errorListener, gatherData, javaProject, markErrors);
parser.parse(new InputSource(manifestFile.getContents()), manifestHandler);
// get the result from the handler
return new AndroidManifestParser(manifestHandler.getPackage(),
manifestHandler.getActivities(), manifestHandler.getLauncherActivity(),
manifestHandler.getProcesses(), manifestHandler.getDebuggable(),
manifestHandler.getApiLevelRequirement());
} catch (ParserConfigurationException e) {
} catch (SAXException e) {
} catch (IOException e) {
} finally {
}
return null;
}
/**
* Parses the Android Manifest for the specified project, and returns an object containing
* the result of the parsing.
* @param javaProject The java project. Required if <var>markErrors</var> is <code>true</code>
* @param errorListener the {@link XmlErrorListener} object being notified of the presence
* of errors. Optional.
* @param gatherData indicates whether the parsing will extract data from the manifest.
* @param markErrors indicates whether the error found during parsing should put a
* marker on the file. For class validation errors to put a marker, <code>gatherData</code>
* must be set to <code>true</code>
* @return an {@link AndroidManifestParser} or null if the parsing failed.
* @throws CoreException
*/
public static AndroidManifestParser parse(IJavaProject javaProject,
XmlErrorListener errorListener, boolean gatherData, boolean markErrors)
throws CoreException {
try {
SAXParser parser = sParserFactory.newSAXParser();
IFile manifestFile = AndroidManifestHelper.getManifest(javaProject.getProject());
if (manifestFile != null) {
ManifestHandler manifestHandler = new ManifestHandler(manifestFile,
errorListener, gatherData, javaProject, markErrors);
parser.parse(new InputSource(manifestFile.getContents()), manifestHandler);
// get the result from the handler
return new AndroidManifestParser(manifestHandler.getPackage(),
manifestHandler.getActivities(), manifestHandler.getLauncherActivity(),
manifestHandler.getProcesses(), manifestHandler.getDebuggable(),
manifestHandler.getApiLevelRequirement());
}
} catch (ParserConfigurationException e) {
} catch (SAXException e) {
} catch (IOException e) {
} finally {
}
return null;
}
/**
* Parses the manifest file, collects data, and checks for errors.
* @param javaProject The java project. Required.
* @param manifestFile
* @param errorListener the {@link XmlErrorListener} object being notified of the presence
* of errors. Optional.
* @return an {@link AndroidManifestParser} or null if the parsing failed.
* @throws CoreException
* @see {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)}
*/
public static AndroidManifestParser parseForError(IJavaProject javaProject, IFile manifestFile,
XmlErrorListener errorListener) throws CoreException {
return parse(javaProject, manifestFile, errorListener, true, true);
}
/**
* Parses the manifest file, and collects data.
* @param manifestFile
* @param errorListener the {@link XmlErrorListener} object being notified of the presence
* of errors. Optional.
* @return an {@link AndroidManifestParser} or null if the parsing failed.
* @throws CoreException
* @see {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)}
*/
public static AndroidManifestParser parseForData(IFile manifestFile) throws CoreException {
return parse(null /* javaProject */, manifestFile, null /* errorListener */,
true /* gatherData */, false /* markErrors */);
}
/**
* Returns the package defined in the manifest, if found.
* @return The package name or null if not found.
*/
public String getPackage() {
return mJavaPackage;
}
/**
* Returns the list of activities found in the manifest.
* @return An array of fully qualified class names, or empty if no activity were found.
*/
public String[] getActivities() {
return mActivities;
}
/**
* Returns the name of one activity found in the manifest, that is configured to show
* up in the HOME screen.
* @return the fully qualified name of a HOME activity or null if none were found.
*/
public String getLauncherActivity() {
return mLauncherActivity;
}
/**
* Returns the list of process names declared by the manifest.
*/
public String[] getProcesses() {
return mProcesses;
}
/**
* Returns the debuggable attribute value or <code>null</code> if it is not set.
*/
public Boolean getDebuggable() {
return mDebuggable;
}
/**
* Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set.
*/
public int getApiLevelRequirement() {
return mApiLevelRequirement;
}
/**
* Private constructor to enforce using
* {@link #parse(IJavaProject, XmlErrorListener, boolean, boolean)},
* {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)},
* or {@link #parseForError(IJavaProject, IFile, XmlErrorListener)} to get an
* {@link AndroidManifestParser} object.
* @param javaPackage the package parsed from the manifest.
* @param activities the list of activities parsed from the manifest.
* @param launcherActivity the launcher activity parser from the manifest.
* @param processes the list of custom processes declared in the manifest.
* @param debuggable the debuggable attribute, or null if not set.
* @param apiLevelRequirement the minSdkVersion attribute value or 0 if not set.
*/
private AndroidManifestParser(String javaPackage, String[] activities,
String launcherActivity, String[] processes, Boolean debuggable,
int apiLevelRequirement) {
mJavaPackage = javaPackage;
mActivities = activities;
mLauncherActivity = launcherActivity;
mProcesses = processes;
mDebuggable = debuggable;
mApiLevelRequirement = apiLevelRequirement;
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.common.project;
import com.android.ide.eclipse.common.AndroidConstants;
import java.util.Iterator;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
/**
* XPath factory with automatic support for the android namespace.
*/
public class AndroidXPathFactory {
public final static String DEFAULT_NS_PREFIX = "android"; //$NON-NLS-1$
private final static XPathFactory sFactory = XPathFactory.newInstance();
/** Namespace context for Android resource XML files. */
private static class AndroidNamespaceContext implements NamespaceContext {
private String mAndroidPrefix;
/**
* Construct the context with the prefix associated with the android namespace.
* @param prefix the Prefix
*/
public AndroidNamespaceContext(String androidPrefix) {
mAndroidPrefix = androidPrefix;
}
public String getNamespaceURI(String prefix) {
if (prefix != null) {
if (prefix.equals(mAndroidPrefix)) {
return AndroidConstants.NS_RESOURCES;
}
}
return XMLConstants.NULL_NS_URI;
}
public String getPrefix(String namespaceURI) {
// This isn't necessary for our use.
assert false;
return null;
}
public Iterator<?> getPrefixes(String namespaceURI) {
// This isn't necessary for our use.
assert false;
return null;
}
}
/**
* Creates a new XPath object, specifying which prefix in the query is used for the
* android namespace.
* @param prefix The namespace prefix.
*/
public static XPath newXPath(String androidPrefix) {
XPath xpath = sFactory.newXPath();
xpath.setNamespaceContext(new AndroidNamespaceContext(androidPrefix));
return xpath;
}
/**
* Creates a new XPath object using the default prefix for the android namespace.
* @see #DEFAULT_NS_PREFIX
*/
public static XPath newXPath() {
return newXPath(DEFAULT_NS_PREFIX);
}
}

View File

@@ -0,0 +1,419 @@
/*
* 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.common.project;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
import java.util.ArrayList;
/**
* Utility methods to manipulate projects.
*/
public final class BaseProjectHelper {
public static final String TEST_CLASS_OK = null;
/**
* returns a list of source classpath for a specified project
* @param javaProject
* @return a list of path relative to the workspace root.
*/
public static ArrayList<IPath> getSourceClasspaths(IJavaProject javaProject) {
ArrayList<IPath> sourceList = new ArrayList<IPath>();
IClasspathEntry[] classpaths = javaProject.readRawClasspath();
if (classpaths != null) {
for (IClasspathEntry e : classpaths) {
if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
sourceList.add(e.getPath());
}
}
}
return sourceList;
}
/**
* Adds a marker to a file on a specific line
* @param file the file to be marked
* @param markerId The id of the marker to add.
* @param message the message associated with the mark
* @param lineNumber the line number where to put the mark
* @param severity the severity of the marker.
* @return the IMarker that was added or null if it failed to add one.
*/
public final static IMarker addMarker(IResource file, String markerId,
String message, int lineNumber, int severity) {
try {
IMarker marker = file.createMarker(markerId);
marker.setAttribute(IMarker.MESSAGE, message);
marker.setAttribute(IMarker.SEVERITY, severity);
if (lineNumber == -1) {
lineNumber = 1;
}
marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
// on Windows, when adding a marker to a project, it takes a refresh for the marker
// to show. In order to fix this we're forcing a refresh of elements receiving
// markers (and only the element, not its children), to force the marker display.
file.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
return marker;
} catch (CoreException e) {
AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'",
markerId, file.getFullPath());
}
return null;
}
/**
* Adds a marker to a resource.
* @param resource the file to be marked
* @param markerId The id of the marker to add.
* @param message the message associated with the mark
* @param severity the severity of the marker.
* @return the IMarker that was added or null if it failed to add one.
*/
public final static IMarker addMarker(IResource resource, String markerId,
String message, int severity) {
try {
IMarker marker = resource.createMarker(markerId);
marker.setAttribute(IMarker.MESSAGE, message);
marker.setAttribute(IMarker.SEVERITY, severity);
// on Windows, when adding a marker to a project, it takes a refresh for the marker
// to show. In order to fix this we're forcing a refresh of elements receiving
// markers (and only the element, not its children), to force the marker display.
resource.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
return marker;
} catch (CoreException e) {
AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'",
markerId, resource.getFullPath());
}
return null;
}
/**
* Tests that a class name is valid for usage in the manifest.
* <p/>
* This tests the class existence, that it can be instantiated (ie it must not be abstract,
* nor non static if enclosed), and that it extends the proper super class (not necessarily
* directly)
* @param javaProject the {@link IJavaProject} containing the class.
* @param className the fully qualified name of the class to test.
* @param superClassName the fully qualified name of the expected super class.
* @param testVisibility if <code>true</code>, the method will check the visibility of the class
* or of its constructors.
* @return {@link #TEST_CLASS_OK} or an error message.
*/
public final static String testClassForManifest(IJavaProject javaProject, String className,
String superClassName, boolean testVisibility) {
try {
// replace $ by .
String javaClassName = className.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS-2$
// look for the IType object for this class
IType type = javaProject.findType(javaClassName);
if (type != null && type.exists()) {
// test that the class is not abstract
int flags = type.getFlags();
if (Flags.isAbstract(flags)) {
return String.format("%1$s is abstract", className);
}
// test whether the class is public or not.
if (testVisibility && Flags.isPublic(flags) == false) {
// if its not public, it may have a public default constructor,
// which would then be fine.
IMethod basicConstructor = type.getMethod(type.getElementName(), new String[0]);
if (basicConstructor != null && basicConstructor.exists()) {
int constructFlags = basicConstructor.getFlags();
if (Flags.isPublic(constructFlags) == false) {
return String.format(
"%1$s or its default constructor must be public for the system to be able to instantiate it",
className);
}
} else {
return String.format(
"%1$s must be public, or the system will not be able to instantiate it.",
className);
}
}
// If it's enclosed, test that it's static. If its declaring class is enclosed
// as well, test that it is also static, and public.
IType declaringType = type;
do {
IType tmpType = declaringType.getDeclaringType();
if (tmpType != null) {
if (tmpType.exists()) {
flags = declaringType.getFlags();
if (Flags.isStatic(flags) == false) {
return String.format("%1$s is enclosed, but not static",
declaringType.getFullyQualifiedName());
}
flags = tmpType.getFlags();
if (testVisibility && Flags.isPublic(flags) == false) {
return String.format("%1$s is not public",
tmpType.getFullyQualifiedName());
}
} else {
// if it doesn't exist, we need to exit so we may as well mark it null.
tmpType = null;
}
}
declaringType = tmpType;
} while (declaringType != null);
// test the class inherit from the specified super class.
// get the type hierarchy
ITypeHierarchy hierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());
// if the super class is not the reference class, it may inherit from
// it so we get its supertype. At some point it will be null and we
// will stop
IType superType = type;
boolean foundProperSuperClass = false;
while ((superType = hierarchy.getSuperclass(superType)) != null &&
superType.exists()) {
if (superClassName.equals(superType.getFullyQualifiedName())) {
foundProperSuperClass = true;
}
}
// didn't find the proper superclass? return false.
if (foundProperSuperClass == false) {
return String.format("%1$s does not extend %2$s", className, superClassName);
}
return TEST_CLASS_OK;
} else {
return String.format("Class %1$s does not exist", className);
}
} catch (JavaModelException e) {
return String.format("%1$s: %2$s", className, e.getMessage());
}
}
/**
* Parses the manifest file for errors.
* <p/>
* This starts by removing the current XML marker, and then parses the xml for errors, both
* of XML type and of Android type (checking validity of class files).
* @param manifestFile
* @param errorListener
* @throws CoreException
*/
public static AndroidManifestParser parseManifestForError(IFile manifestFile,
XmlErrorListener errorListener) throws CoreException {
// remove previous markers
if (manifestFile.exists()) {
manifestFile.deleteMarkers(AndroidConstants.MARKER_XML, true, IResource.DEPTH_ZERO);
manifestFile.deleteMarkers(AndroidConstants.MARKER_ANDROID, true, IResource.DEPTH_ZERO);
}
// and parse
return AndroidManifestParser.parseForError(
BaseProjectHelper.getJavaProject(manifestFile.getProject()),
manifestFile, errorListener);
}
/**
* Returns the {@link IJavaProject} for a {@link IProject} object.
* <p/>
* This checks if the project has the Java Nature first.
* @param project
* @return the IJavaProject or null if the project couldn't be created or if the project
* does not have the Java Nature.
* @throws CoreException
*/
public static IJavaProject getJavaProject(IProject project) throws CoreException {
if (project != null && project.hasNature(JavaCore.NATURE_ID)) {
return JavaCore.create(project);
}
return null;
}
/**
* Reveals a specific line in the source file defining a specified class,
* for a specific project.
* @param project
* @param className
* @param line
*/
public static void revealSource(IProject project, String className, int line) {
// in case the type is enclosed, we need to replace the $ with .
className = className.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS2$
// get the java project
IJavaProject javaProject = JavaCore.create(project);
try {
// look for the IType matching the class name.
IType result = javaProject.findType(className);
if (result != null && result.exists()) {
// before we show the type in an editor window, we make sure the current
// workbench page has an editor area (typically the ddms perspective doesn't).
IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
IWorkbenchPage page = window.getActivePage();
if (page.isEditorAreaVisible() == false) {
// no editor area? we open the java perspective.
new OpenJavaPerspectiveAction().run();
}
IEditorPart editor = JavaUI.openInEditor(result);
if (editor instanceof ITextEditor) {
// get the text editor that was just opened.
ITextEditor textEditor = (ITextEditor)editor;
IEditorInput input = textEditor.getEditorInput();
// get the location of the line to show.
IDocumentProvider documentProvider = textEditor.getDocumentProvider();
IDocument document = documentProvider.getDocument(input);
IRegion lineInfo = document.getLineInformation(line - 1);
// select and reveal the line.
textEditor.selectAndReveal(lineInfo.getOffset(), lineInfo.getLength());
}
}
} catch (JavaModelException e) {
} catch (PartInitException e) {
} catch (BadLocationException e) {
}
}
/**
* Returns the list of android-flagged projects. This list contains projects that are opened
* in the workspace and that are flagged as android project (through the android nature)
* @return an array of IJavaProject, which can be empty if no projects match.
*/
public static IJavaProject[] getAndroidProjects() {
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
IJavaModel javaModel = JavaCore.create(workspaceRoot);
return getAndroidProjects(javaModel);
}
/**
* Returns the list of android-flagged projects for the specified java Model.
* This list contains projects that are opened in the workspace and that are flagged as android
* project (through the android nature)
* @param javaModel the Java Model object corresponding for the current workspace root.
* @return an array of IJavaProject, which can be empty if no projects match.
*/
public static IJavaProject[] getAndroidProjects(IJavaModel javaModel) {
// get the java projects
IJavaProject[] javaProjectList = null;
try {
javaProjectList = javaModel.getJavaProjects();
}
catch (JavaModelException jme) {
return new IJavaProject[0];
}
// temp list to build the android project array
ArrayList<IJavaProject> androidProjectList = new ArrayList<IJavaProject>();
// loop through the projects and add the android flagged projects to the temp list.
for (IJavaProject javaProject : javaProjectList) {
// get the workspace project object
IProject project = javaProject.getProject();
// check if it's an android project based on its nature
try {
if (project.hasNature(AndroidConstants.NATURE)) {
androidProjectList.add(javaProject);
}
} catch (CoreException e) {
// this exception, thrown by IProject.hasNature(), means the project either doesn't
// exist or isn't opened. So, in any case we just skip it (the exception will
// bypass the ArrayList.add()
}
}
// return the android projects list.
return androidProjectList.toArray(new IJavaProject[androidProjectList.size()]);
}
/**
* Returns the {@link IFolder} representing the output for the project.
* <p>
* The project must be a java project and be opened, or the method will return null.
* @param project the {@link IProject}
* @return an IFolder item or null.
*/
public final static IFolder getOutputFolder(IProject project) {
try {
if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
// get a java project from the normal project object
IJavaProject javaProject = JavaCore.create(project);
IPath path = javaProject.getOutputLocation();
IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
IResource outputResource = wsRoot.findMember(path);
if (outputResource != null && outputResource.getType() == IResource.FOLDER) {
return (IFolder)outputResource;
}
}
} catch (JavaModelException e) {
// Let's do nothing and return null
} catch (CoreException e) {
// Let's do nothing and return null
}
return null;
}
}

View File

@@ -0,0 +1,188 @@
/*
* 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.common.project;
import com.android.ide.eclipse.common.AndroidConstants;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Shell;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.jar.JarEntry;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* Export helper for project.
*/
public final class ExportHelper {
private static IExportCallback sCallback;
public interface IExportCallback {
void startExportWizard(IProject project);
}
public static void setCallback(IExportCallback callback) {
sCallback = callback;
}
public static void startExportWizard(IProject project) {
if (sCallback != null) {
sCallback.startExportWizard(project);
}
}
/**
* Exports an <b>unsigned</b> version of the application created by the given project.
* @param project the project to export
*/
public static void exportProject(IProject project) {
Shell shell = Display.getCurrent().getActiveShell();
// get the java project to get the output directory
IFolder outputFolder = BaseProjectHelper.getOutputFolder(project);
if (outputFolder != null) {
IPath binLocation = outputFolder.getLocation();
// make the full path to the package
String fileName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE;
File file = new File(binLocation.toOSString() + File.separator + fileName);
if (file.exists() == false || file.isFile() == false) {
MessageDialog.openInformation(Display.getCurrent().getActiveShell(),
"Android IDE Plug-in",
String.format("Failed to export %1$s: %2$s doesn't exist!",
project.getName(), file.getPath()));
return;
}
// ok now pop up the file save window
FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
fileDialog.setText("Export Project");
fileDialog.setFileName(fileName);
String saveLocation = fileDialog.open();
if (saveLocation != null) {
// get the stream from the original file
ZipInputStream zis = null;
ZipOutputStream zos = null;
FileInputStream input = null;
FileOutputStream output = null;
try {
input = new FileInputStream(file);
zis = new ZipInputStream(input);
// get an output stream into the new file
File saveFile = new File(saveLocation);
output = new FileOutputStream(saveFile);
zos = new ZipOutputStream(output);
} catch (FileNotFoundException e) {
// only the input/output stream are throwing this exception.
// so we only have to close zis if output is the one that threw.
if (zis != null) {
try {
zis.close();
} catch (IOException e1) {
// pass
}
}
MessageDialog.openInformation(shell, "Android IDE Plug-in",
String.format("Failed to export %1$s: %2$s doesn't exist!",
project.getName(), file.getPath()));
return;
}
try {
ZipEntry entry;
byte[] buffer = new byte[4096];
while ((entry = zis.getNextEntry()) != null) {
String name = entry.getName();
// do not take directories or anything inside the META-INF folder since
// we want to strip the signature.
if (entry.isDirectory() || name.startsWith("META-INF/")) { //$NON-NL1$
continue;
}
ZipEntry newEntry;
// Preserve the STORED method of the input entry.
if (entry.getMethod() == JarEntry.STORED) {
newEntry = new JarEntry(entry);
} else {
// Create a new entry so that the compressed len is recomputed.
newEntry = new JarEntry(name);
}
// add the entry to the jar archive
zos.putNextEntry(newEntry);
// read the content of the entry from the input stream, and write it into the archive.
int count;
while ((count = zis.read(buffer)) != -1) {
zos.write(buffer, 0, count);
}
// close the entry for this file
zos.closeEntry();
zis.closeEntry();
}
} catch (IOException e) {
MessageDialog.openInformation(shell, "Android IDE Plug-in",
String.format("Failed to export %1$s: %2$s",
project.getName(), e.getMessage()));
} finally {
try {
zos.close();
} catch (IOException e) {
// pass
}
try {
zis.close();
} catch (IOException e) {
// pass
}
}
}
} else {
MessageDialog.openInformation(shell, "Android IDE Plug-in",
String.format("Failed to export %1$s: Could not get project output location",
project.getName()));
}
}
}

View File

@@ -0,0 +1,110 @@
/*
* 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.common.project;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
/**
* Helper class to deal with displaying a project choosing dialog that lists only the
* projects with the Android nature.
*/
public class ProjectChooserHelper {
private final Shell mParentShell;
/**
* List of current android projects. Since the dialog is modal, we'll just get
* the list once on-demand.
*/
private IJavaProject[] mAndroidProjects;
public ProjectChooserHelper(Shell parentShell) {
mParentShell = parentShell;
}
/**
* Displays a project chooser dialog which lists all available projects with the Android nature.
* <p/>
* The list of project is built from Android flagged projects currently opened in the workspace.
*
* @param projectName If non null and not empty, represents the name of an Android project
* that will be selected by default.
* @return the project chosen by the user in the dialog, or null if the dialog was canceled.
*/
public IJavaProject chooseJavaProject(String projectName) {
ILabelProvider labelProvider = new JavaElementLabelProvider(
JavaElementLabelProvider.SHOW_DEFAULT);
ElementListSelectionDialog dialog = new ElementListSelectionDialog(
mParentShell, labelProvider);
dialog.setTitle("Project Selection");
dialog.setMessage("Select a project to constrain your search.");
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
IJavaModel javaModel = JavaCore.create(workspaceRoot);
// set the elements in the dialog. These are opened android projects.
dialog.setElements(getAndroidProjects(javaModel));
// look for the project matching the given project name
IJavaProject javaProject = null;
if (projectName != null && projectName.length() > 0) {
javaProject = javaModel.getJavaProject(projectName);
}
// if we found it, we set the initial selection in the dialog to this one.
if (javaProject != null) {
dialog.setInitialSelections(new Object[] { javaProject });
}
// open the dialog and return the object selected if OK was clicked, or null otherwise
if (dialog.open() == Window.OK) {
return (IJavaProject)dialog.getFirstResult();
}
return null;
}
/**
* Returns the list of Android projects.
* <p/>
* Because this list can be time consuming, this class caches the list of project.
* It is recommended to call this method instead of
* {@link BaseProjectHelper#getAndroidProjects()}.
*
* @param javaModel the java model. Can be null.
*/
public IJavaProject[] getAndroidProjects(IJavaModel javaModel) {
if (mAndroidProjects == null) {
if (javaModel == null) {
mAndroidProjects = BaseProjectHelper.getAndroidProjects();
} else {
mAndroidProjects = BaseProjectHelper.getAndroidProjects(javaModel);
}
}
return mAndroidProjects;
}
}

View File

@@ -0,0 +1,112 @@
/*
* 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.common.project;
import com.android.ide.eclipse.common.AndroidConstants;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* XML error handler used by the parser to report errors/warnings.
*/
public class XmlErrorHandler extends DefaultHandler {
/** file being parsed */
private IFile mFile;
/** link to the delta visitor, to set the xml error flag */
private XmlErrorListener mErrorListener;
/**
* Classes which implement this interface provide a method that deals
* with XML errors.
*/
public interface XmlErrorListener {
/**
* Sent when an XML error is detected.
*/
public void errorFound();
}
public static class BasicXmlErrorListener implements XmlErrorListener {
public boolean mHasXmlError = false;
public void errorFound() {
mHasXmlError = true;
}
}
public XmlErrorHandler(IFile file, XmlErrorListener errorListener) {
mFile = file;
mErrorListener = errorListener;
}
/**
* Xml Error call back
* @param exception the parsing exception
*/
@Override
public void error(SAXParseException exception) throws SAXException {
handleError(exception, exception.getLineNumber());
}
/**
* Xml Fatal Error call back
* @param exception the parsing exception
*/
@Override
public void fatalError(SAXParseException exception) throws SAXException {
handleError(exception, exception.getLineNumber());
}
/**
* Xml Warning call back
* @param exception the parsing exception
*/
@Override
public void warning(SAXParseException exception) throws SAXException {
BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(),
exception.getLineNumber(), IMarker.SEVERITY_WARNING);
}
protected final IFile getFile() {
return mFile;
}
/**
* Handles a parsing error and an optional line number.
* @param exception
* @param lineNumber
*/
protected void handleError(Exception exception, int lineNumber) {
if (mErrorListener != null) {
mErrorListener.errorFound();
}
if (lineNumber != -1) {
BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(),
lineNumber, IMarker.SEVERITY_ERROR);
} else {
BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(),
IMarker.SEVERITY_ERROR);
}
}
}

View File

@@ -0,0 +1,461 @@
/*
* 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.common.resources;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo;
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo.Format;
import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo;
import org.eclipse.core.runtime.IStatus;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
* Parser for attributes description files.
*/
public final class AttrsXmlParser {
private Document mDocument;
private String mOsAttrsXmlPath;
// all attributes that have the same name are supposed to have the same
// parameters so we'll keep a cache of them to avoid processing them twice.
private HashMap<String, AttributeInfo> mAttributeMap;
/** Map of all attribute names for a given element */
private HashMap<String, DeclareStyleableInfo> mStyleMap;
/** Map of all (constant, value) pairs for attributes of format enum or flag.
* E.g. for attribute name=gravity, this tells us there's an enum/flag called "center"
* with value 0x11.
*/
private Map<String, Map<String, Integer>> mEnumFlagValues;
/**
* Creates a new {@link AttrsXmlParser}, set to load things from the given
* XML file. Nothing has been parsed yet. Callers should call {@link #preload()}
* next.
*/
public AttrsXmlParser(String osAttrsXmlPath) {
this(osAttrsXmlPath, null /* inheritableAttributes */);
}
/**
* Creates a new {@link AttrsXmlParser} set to load things from the given
* XML file. If inheritableAttributes is non-null, it must point to a preloaded
* {@link AttrsXmlParser} which attributes will be used for this one. Since
* already defined attributes are not modifiable, they are thus "inherited".
*/
public AttrsXmlParser(String osAttrsXmlPath, AttrsXmlParser inheritableAttributes) {
mOsAttrsXmlPath = osAttrsXmlPath;
// styles are not inheritable.
mStyleMap = new HashMap<String, DeclareStyleableInfo>();
if (inheritableAttributes == null) {
mAttributeMap = new HashMap<String, AttributeInfo>();
mEnumFlagValues = new HashMap<String, Map<String,Integer>>();
} else {
mAttributeMap = new HashMap<String, AttributeInfo>(inheritableAttributes.mAttributeMap);
mEnumFlagValues = new HashMap<String, Map<String,Integer>>(
inheritableAttributes.mEnumFlagValues);
}
}
/**
* @return The OS path of the attrs.xml file parsed
*/
public String getOsAttrsXmlPath() {
return mOsAttrsXmlPath;
}
/**
* Preloads the document, parsing all attributes and declared styles.
*
* @return Self, for command chaining.
*/
public AttrsXmlParser preload() {
Document doc = getDocument();
if (doc == null) {
AdtPlugin.log(IStatus.WARNING, "Failed to find %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
return this;
}
Node res = doc.getFirstChild();
while (res != null &&
res.getNodeType() != Node.ELEMENT_NODE &&
!res.getNodeName().equals("resources")) { //$NON-NLS-1$
res = res.getNextSibling();
}
if (res == null) {
AdtPlugin.log(IStatus.WARNING, "Failed to find a <resources> node in %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
return this;
}
parseResources(res);
return this;
}
/**
* Loads all attributes & javadoc for the view class info based on the class name.
*/
public void loadViewAttributes(ViewClassInfo info) {
if (getDocument() != null) {
String xmlName = info.getShortClassName();
DeclareStyleableInfo style = mStyleMap.get(xmlName);
if (style != null) {
info.setAttributes(style.getAttributes());
info.setJavaDoc(style.getJavaDoc());
}
}
}
/**
* Loads all attributes for the layout data info based on the class name.
*/
public void loadLayoutParamsAttributes(LayoutParamsInfo info) {
if (getDocument() != null) {
// Transforms "LinearLayout" and "LayoutParams" into "LinearLayout_Layout".
String xmlName = String.format("%1$s_%2$s", //$NON-NLS-1$
info.getViewLayoutClass().getShortClassName(),
info.getShortClassName());
xmlName = xmlName.replaceFirst("Params$", ""); //$NON-NLS-1$ //$NON-NLS-2$
DeclareStyleableInfo style = mStyleMap.get(xmlName);
if (style != null) {
info.setAttributes(style.getAttributes());
}
}
}
/**
* Returns a list of all decleare-styleable found in the xml file.
*/
public Map<String, DeclareStyleableInfo> getDeclareStyleableList() {
return Collections.unmodifiableMap(mStyleMap);
}
/**
* Returns a map of all enum and flag constants sorted by parent attribute name.
* The map is attribute_name => (constant_name => integer_value).
*/
public Map<String, Map<String, Integer>> getEnumFlagValues() {
return mEnumFlagValues;
}
//-------------------------
/**
* Creates an XML document from the attrs.xml OS path.
* May return null if the file doesn't exist or cannot be parsed.
*/
private Document getDocument() {
if (mDocument == null) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setIgnoringComments(false);
try {
DocumentBuilder builder = factory.newDocumentBuilder();
mDocument = builder.parse(new File(mOsAttrsXmlPath));
} catch (ParserConfigurationException e) {
AdtPlugin.log(e, "Failed to create XML document builder for %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
} catch (SAXException e) {
AdtPlugin.log(e, "Failed to parse XML document %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
} catch (IOException e) {
AdtPlugin.log(e, "Failed to read XML document %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
}
}
return mDocument;
}
/**
* Finds all the <declare-styleable> and <attr> nodes in the top <resources> node.
*/
private void parseResources(Node res) {
Node lastComment = null;
for (Node node = res.getFirstChild(); node != null; node = node.getNextSibling()) {
switch (node.getNodeType()) {
case Node.COMMENT_NODE:
lastComment = node;
break;
case Node.ELEMENT_NODE:
if (node.getNodeName().equals("declare-styleable")) { //$NON-NLS-1$
Node nameNode = node.getAttributes().getNamedItem("name"); //$NON-NLS-1$
if (nameNode != null) {
String name = nameNode.getNodeValue();
Node parentNode = node.getAttributes().getNamedItem("parent"); //$NON-NLS-1$
String parents = parentNode == null ? null : parentNode.getNodeValue();
if (name != null && !mStyleMap.containsKey(name)) {
DeclareStyleableInfo style = parseDeclaredStyleable(name, node);
if (parents != null) {
style.setParents(parents.split("[ ,|]")); //$NON-NLS-1$
}
mStyleMap.put(name, style);
if (lastComment != null) {
style.setJavaDoc(formatJavadoc(lastComment.getNodeValue()));
}
}
}
} else if (node.getNodeName().equals("attr")) { //$NON-NLS-1$
parseAttr(node, lastComment);
}
lastComment = null;
break;
}
}
}
/**
* Parses an <attr> node and convert it into an {@link AttributeInfo} if it is valid.
*/
private AttributeInfo parseAttr(Node attrNode, Node lastComment) {
AttributeInfo info = null;
Node nameNode = attrNode.getAttributes().getNamedItem("name"); //$NON-NLS-1$
if (nameNode != null) {
String name = nameNode.getNodeValue();
if (name != null) {
info = mAttributeMap.get(name);
// If the attribute is unknown yet, parse it.
// If the attribute is know but its format is unknown, parse it too.
if (info == null || info.getFormats().length == 0) {
info = parseAttributeTypes(attrNode, name);
if (info != null) {
mAttributeMap.put(name, info);
}
} else if (lastComment != null) {
info = new AttributeInfo(info);
}
if (info != null) {
if (lastComment != null) {
info.setJavaDoc(formatJavadoc(lastComment.getNodeValue()));
}
}
}
}
return info;
}
/**
* Finds all the attributes for a particular style node,
* e.g. a declare-styleable of name "TextView" or "LinearLayout_Layout".
*
* @param styleName The name of the declare-styleable node
* @param declareStyleableNode The declare-styleable node itself
*/
private DeclareStyleableInfo parseDeclaredStyleable(String styleName,
Node declareStyleableNode) {
ArrayList<AttributeInfo> attrs = new ArrayList<AttributeInfo>();
Node lastComment = null;
for (Node node = declareStyleableNode.getFirstChild();
node != null;
node = node.getNextSibling()) {
switch (node.getNodeType()) {
case Node.COMMENT_NODE:
lastComment = node;
break;
case Node.ELEMENT_NODE:
if (node.getNodeName().equals("attr")) { //$NON-NLS-1$
AttributeInfo info = parseAttr(node, lastComment);
if (info != null) {
attrs.add(info);
}
}
lastComment = null;
break;
}
}
return new DeclareStyleableInfo(styleName, attrs.toArray(new AttributeInfo[attrs.size()]));
}
/**
* Returns the {@link AttributeInfo} for a specific <attr> XML node.
* This gets the javadoc, the type, the name and the enum/flag values if any.
* <p/>
* The XML node is expected to have the following attributes:
* <ul>
* <li>"name", which is mandatory. The node is skipped if this is missing.</li>
* <li>"format".</li>
* </ul>
* The format may be one type or two types (e.g. "reference|color").
* An extra format can be implied: "enum" or "flag" are not specified in the "format" attribute,
* they are implicitely stated by the presence of sub-nodes <enum> or <flag>.
* <p/>
* By design, <attr> nodes of the same name MUST have the same type.
* Attribute nodes are thus cached by name and reused as much as possible.
* When reusing a node, it is duplicated and its javadoc reassigned.
*/
private AttributeInfo parseAttributeTypes(Node attrNode, String name) {
TreeSet<AttributeInfo.Format> formats = new TreeSet<AttributeInfo.Format>();
String[] enumValues = null;
String[] flagValues = null;
Node attrFormat = attrNode.getAttributes().getNamedItem("format"); //$NON-NLS-1$
if (attrFormat != null) {
for (String f : attrFormat.getNodeValue().split("\\|")) { //$NON-NLS-1$
try {
Format format = AttributeInfo.Format.valueOf(f.toUpperCase());
// enum and flags are handled differently right below
if (format != null &&
format != AttributeInfo.Format.ENUM &&
format != AttributeInfo.Format.FLAG) {
formats.add(format);
}
} catch (IllegalArgumentException e) {
AdtPlugin.log(e, "Unknown format name '%s' in <attr name=\"%s\">, file '%s'.", //$NON-NLS-1$
f, name, getOsAttrsXmlPath());
}
}
}
// does this <attr> have <enum> children?
enumValues = parseEnumFlagValues(attrNode, "enum", name); //$NON-NLS-1$
if (enumValues != null) {
formats.add(AttributeInfo.Format.ENUM);
}
// does this <attr> have <flag> children?
flagValues = parseEnumFlagValues(attrNode, "flag", name); //$NON-NLS-1$
if (flagValues != null) {
formats.add(AttributeInfo.Format.FLAG);
}
AttributeInfo info = new AttributeInfo(name,
formats.toArray(new AttributeInfo.Format[formats.size()]));
info.setEnumValues(enumValues);
info.setFlagValues(flagValues);
return info;
}
/**
* Given an XML node that represents an <attr> node, this method searches
* if the node has any children nodes named "target" (e.g. "enum" or "flag").
* Such nodes must have a "name" attribute.
* <p/>
* If "attrNode" is null, look for any <attr> that has the given attrNode
* and the requested children nodes.
* <p/>
* This method collects all the possible names of these children nodes and
* return them.
*
* @param attrNode The <attr> XML node
* @param filter The child node to look for, either "enum" or "flag".
* @param attrName The value of the name attribute of <attr>
*
* @return Null if there are no such children nodes, otherwise an array of length >= 1
* of all the names of these children nodes.
*/
private String[] parseEnumFlagValues(Node attrNode, String filter, String attrName) {
ArrayList<String> names = null;
for (Node child = attrNode.getFirstChild(); child != null; child = child.getNextSibling()) {
if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(filter)) {
Node nameNode = child.getAttributes().getNamedItem("name"); //$NON-NLS-1$
if (nameNode == null) {
AdtPlugin.log(IStatus.WARNING,
"Missing name attribute in <attr name=\"%s\"><%s></attr>", //$NON-NLS-1$
attrName, filter);
} else {
if (names == null) {
names = new ArrayList<String>();
}
String name = nameNode.getNodeValue();
names.add(name);
Node valueNode = child.getAttributes().getNamedItem("value"); //$NON-NLS-1$
if (valueNode == null) {
AdtPlugin.log(IStatus.WARNING,
"Missing value attribute in <attr name=\"%s\"><%s name=\"%s\"></attr>", //$NON-NLS-1$
attrName, filter, name);
} else {
String value = valueNode.getNodeValue();
try {
int i = value.startsWith("0x") ?
Integer.parseInt(value.substring(2), 16 /* radix */) :
Integer.parseInt(value);
Map<String, Integer> map = mEnumFlagValues.get(attrName);
if (map == null) {
map = new HashMap<String, Integer>();
mEnumFlagValues.put(attrName, map);
}
map.put(name, Integer.valueOf(i));
} catch(NumberFormatException e) {
AdtPlugin.log(e,
"Value in <attr name=\"%s\"><%s name=\"%s\" value=\"%s\"></attr> is not a valid decimal or hexadecimal", //$NON-NLS-1$
attrName, filter, name, value);
}
}
}
}
}
return names == null ? null : names.toArray(new String[names.size()]);
}
/**
* Formats the javadoc.
* Only keeps the first sentence.
* Removes and simplifies links and references.
*/
private String formatJavadoc(String comment) {
if (comment == null) {
return null;
}
// sanitize & collapse whitespace
comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$
// take everything up to the first dot that is followed by a space or the end of the line.
// I love regexps :-). For the curious, the regexp is:
// - start of line
// - ignore whitespace
// - group:
// - everything, not greedy
// - non-capturing group (?: )
// - end of string
// or
// - not preceded by a letter, a dot and another letter (for "i.e" and "e.g" )
// (<! non-capturing zero-width negative look-behind)
// - a dot
// - followed by a space (?= non-capturing zero-width positive look-ahead)
// - anything else is ignored
comment = comment.replaceFirst("^\\s*(.*?(?:$|(?<![a-zA-Z]\\.[a-zA-Z])\\.(?=\\s))).*", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
return comment;
}
}

View File

@@ -0,0 +1,163 @@
/*
* 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.common.resources;
/**
* Information needed to represent a View or ViewGroup (aka Layout) item
* in the layout hierarchy, as extracted from the main android.jar and the
* associated attrs.xml.
*/
public class DeclareStyleableInfo {
/** The style name, never null. */
private String mStyleName;
/** Attributes for this view or view group. Can be empty but never null. */
private AttributeInfo[] mAttributes;
/** Short javadoc. Can be null. */
private String mJavaDoc;
/** Optional name of the parents stylable. Can be null. */
private String[] mParents;
public static class AttributeInfo {
/** XML Name of the attribute */
private String mName;
public enum Format {
STRING,
BOOLEAN,
INTEGER,
FLOAT,
REFERENCE,
COLOR,
DIMENSION,
FRACTION,
ENUM,
FLAG,
}
/** Formats of the attribute. Cannot be null. Should have at least one format. */
private Format[] mFormats;
/** Values for enum. null for other types. */
private String[] mEnumValues;
/** Values for flag. null for other types. */
private String[] mFlagValues;
/** Short javadoc */
private String mJavaDoc;
/**
* @param name The XML Name of the attribute
* @param formats The formats of the attribute. Cannot be null.
* Should have at least one format.
*/
public AttributeInfo(String name, Format[] formats) {
mName = name;
mFormats = formats;
}
public AttributeInfo(AttributeInfo info) {
mName = info.mName;
mFormats = info.mFormats;
mEnumValues = info.mEnumValues;
mFlagValues = info.mFlagValues;
mJavaDoc = info.mJavaDoc;
}
/** Returns the XML Name of the attribute */
public String getName() {
return mName;
}
/** Returns the formats of the attribute. Cannot be null.
* Should have at least one format. */
public Format[] getFormats() {
return mFormats;
}
/** Returns the values for enums. null for other types. */
public String[] getEnumValues() {
return mEnumValues;
}
/** Returns the values for flags. null for other types. */
public String[] getFlagValues() {
return mFlagValues;
}
/** Returns a short javadoc */
public String getJavaDoc() {
return mJavaDoc;
}
/** Sets the values for enums. null for other types. */
public void setEnumValues(String[] values) {
mEnumValues = values;
}
/** Sets the values for flags. null for other types. */
public void setFlagValues(String[] values) {
mFlagValues = values;
}
/** Sets a short javadoc */
public void setJavaDoc(String javaDoc) {
mJavaDoc = javaDoc;
}
}
// --------
/**
* Creates a new {@link DeclareStyleableInfo}.
*
* @param styleName The name of the style. Should not be empty nor null.
* @param attributes The initial list of attributes. Can be null.
*/
public DeclareStyleableInfo(String styleName, AttributeInfo[] attributes) {
mStyleName = styleName;
mAttributes = attributes == null ? new AttributeInfo[0] : attributes;
}
/** Returns style name */
public String getStyleName() {
return mStyleName;
}
/** Returns the attributes for this view or view group. Maybe empty but not null. */
public AttributeInfo[] getAttributes() {
return mAttributes;
}
/** Sets the list of attributes for this View or ViewGroup. */
public void setAttributes(AttributeInfo[] attributes) {
mAttributes = attributes;
}
/** Returns a short javadoc */
public String getJavaDoc() {
return mJavaDoc;
}
/** Sets the javadoc. */
public void setJavaDoc(String javaDoc) {
mJavaDoc = javaDoc;
}
/** Sets the name of the parents styleable. Can be null. */
public void setParents(String[] parents) {
mParents = parents;
}
/** Returns the name of the parents styleable. Can be null. */
public String[] getParents() {
return mParents;
}
}

View File

@@ -0,0 +1,28 @@
/*
* 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.common.resources;
/**
* Classes which implements this interface provides a method indicating the state of a resource of
* type {@link ResourceType#ID}.
*/
public interface IIdResourceItem {
/**
* Returns whether the ID resource has been declared inline inside another resource XML file.
*/
public boolean isDeclaredInline();
}

View File

@@ -0,0 +1,29 @@
/*
* 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.common.resources;
/**
* Classes which implement this interface provide a method that deals with
* a path change.
*/
public interface IPathChangedListener {
/**
* Sent when the location of the android sdk directory changed.
* @param osPath The new android sdk directory location.
*/
public void pathChanged(String osPath);
}

View File

@@ -0,0 +1,47 @@
/*
* 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.common.resources;
/**
* A repository of resources. This allows access to the resource by {@link ResourceType}.
*/
public interface IResourceRepository {
/**
* Returns the present {@link ResourceType}s in the project.
* @return an array containing all the type of resources existing in the project.
*/
public abstract ResourceType[] getAvailableResourceTypes();
/**
* Returns an array of the existing resource for the specified type.
* @param type the type of the resources to return
*/
public abstract ResourceItem[] getResources(ResourceType type);
/**
* Returns whether resources of the specified type are present.
* @param type the type of the resources to check.
*/
public abstract boolean hasResources(ResourceType type);
/**
* Returns whether the repository is a system repository.
*/
public abstract boolean isSystemRepository();
}

View File

@@ -0,0 +1,48 @@
/*
* 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.common.resources;
/**
* Base class representing a Resource Item, as returned by a {@link IResourceRepository}.
*/
public class ResourceItem implements Comparable<ResourceItem> {
private final String mName;
/**
* Constructs a new ResourceItem
* @param name the name of the resource as it appears in the XML and R.java files.
*/
public ResourceItem(String name) {
mName = name;
}
/**
* Returns the name of the resource item.
*/
public final String getName() {
return mName;
}
/**
* Compares the {@link ResourceItem} to another.
* @param other the ResourceItem to be compared to.
*/
public int compareTo(ResourceItem other) {
return mName.compareTo(other.mName);
}
}

View File

@@ -0,0 +1,111 @@
/*
* 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.common.resources;
/**
* Enum representing a type of compiled resource.
*/
public enum ResourceType {
ANIM("anim", "Animation"), //$NON-NLS-1$
ARRAY("array", "Array", "string-array", "integer-array"), //$NON-NLS-1$ //$NON-NLS-3$ //$NON-NLS-4$
ATTR("attr", "Attr"), //$NON-NLS-1$
COLOR("color", "Color"), //$NON-NLS-1$
DIMEN("dimen", "Dimension"), //$NON-NLS-1$
DRAWABLE("drawable", "Drawable"), //$NON-NLS-1$
ID("id", "ID"), //$NON-NLS-1$
LAYOUT("layout", "Layout"), //$NON-NLS-1$
MENU("menu", "Menu"), //$NON-NLS-1$
RAW("raw", "Raw"), //$NON-NLS-1$
STRING("string", "String"), //$NON-NLS-1$
STYLE("style", "Style"), //$NON-NLS-1$
STYLEABLE("styleable", "Styleable"), //$NON-NLS-1$
XML("xml", "XML"); //$NON-NLS-1$
private final String mName;
private final String mDisplayName;
private final String[] mAlternateXmlNames;
ResourceType(String name, String displayName, String... alternateXmlNames) {
mName = name;
mDisplayName = displayName;
mAlternateXmlNames = alternateXmlNames;
}
/**
* Returns the resource type name, as used by XML files.
*/
public String getName() {
return mName;
}
/**
* Returns a translated display name for the resource type.
*/
public String getDisplayName() {
return mDisplayName;
}
/**
* Returns the enum by its name as it appears in the XML or the R class.
* @param name name of the resource
* @return the matching {@link ResourceType} or <code>null</code> if no match was found.
*/
public static ResourceType getEnum(String name) {
for (ResourceType rType : values()) {
if (rType.mName.equals(name)) {
return rType;
} else if (rType.mAlternateXmlNames != null) {
// if there are alternate Xml Names, we test those too
for (String alternate : rType.mAlternateXmlNames) {
if (alternate.equals(name)) {
return rType;
}
}
}
}
return null;
}
/**
* Returns a formatted string usable in an XML to use the specified {@link ResourceItem}.
* @param resourceItem The resource item.
* @param system Whether this is a system resource or a project resource.
* @return a string in the format @[type]/[name]
*/
public String getXmlString(ResourceItem resourceItem, boolean system) {
if (this == ID && resourceItem instanceof IIdResourceItem) {
IIdResourceItem idResource = (IIdResourceItem)resourceItem;
if (idResource.isDeclaredInline()) {
return (system?"@android:":"@+") + mName + "/" + resourceItem.getName(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
return (system?"@android:":"@") + mName + "/" + resourceItem.getName(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/**
* Returns an array with all the names defined by this enum.
*/
public static String[] getNames() {
ResourceType[] values = values();
String[] names = new String[values.length];
for (int i = values.length - 1; i >= 0; --i) {
names[i] = values[i].getName();
}
return names;
}
}

View File

@@ -0,0 +1,159 @@
/*
* 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.common.resources;
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo;
/**
* Information needed to represent a View or ViewGroup (aka Layout) item
* in the layout hierarchy, as extracted from the main android.jar and the
* associated attrs.xml.
*/
public class ViewClassInfo {
/** Is this a layout class (i.e. ViewGroup) or just a view? */
private boolean mIsLayout;
/** FQCN e.g. android.view.View, never null. */
private String mCanonicalClassName;
/** Short class name, e.g. View, never null. */
private String mShortClassName;
/** Super class. Can be null. */
private ViewClassInfo mSuperClass;
/** Short javadoc. Can be null. */
private String mJavaDoc;
/** Attributes for this view or view group. Can be empty but never null. */
private AttributeInfo[] mAttributes;
public static class LayoutParamsInfo {
/** Short class name, e.g. LayoutData, never null. */
private String mShortClassName;
/** ViewLayout class info owning this layout data */
private ViewClassInfo mViewLayoutClass;
/** Super class. Can be null. */
private LayoutParamsInfo mSuperClass;
/** Layout Data Attributes for layout classes. Can be empty but not null. */
private AttributeInfo[] mAttributes;
public LayoutParamsInfo(ViewClassInfo enclosingViewClassInfo,
String shortClassName, LayoutParamsInfo superClassInfo) {
mShortClassName = shortClassName;
mViewLayoutClass = enclosingViewClassInfo;
mSuperClass = superClassInfo;
mAttributes = new AttributeInfo[0];
}
/** Returns short class name, e.g. "LayoutData" */
public String getShortClassName() {
return mShortClassName;
}
/** Returns the ViewLayout class info enclosing this layout data. Cannot null. */
public ViewClassInfo getViewLayoutClass() {
return mViewLayoutClass;
}
/** Returns the super class info. Can be null. */
public LayoutParamsInfo getSuperClass() {
return mSuperClass;
}
/** Returns the LayoutData attributes. Can be empty but not null. */
public AttributeInfo[] getAttributes() {
return mAttributes;
}
/** Sets the LayoutData attributes. Can be empty but not null. */
public void setAttributes(AttributeInfo[] attributes) {
mAttributes = attributes;
}
}
/** Layout data info for a layout class. Null for all non-layout classes and always
* non-null for a layout class. */
public LayoutParamsInfo mLayoutData;
// --------
public ViewClassInfo(boolean isLayout, String canonicalClassName, String shortClassName) {
mIsLayout = isLayout;
mCanonicalClassName = canonicalClassName;
mShortClassName = shortClassName;
mAttributes = new AttributeInfo[0];
}
/** Returns whether this is a layout class (i.e. ViewGroup) or just a View */
public boolean isLayout() {
return mIsLayout;
}
/** Returns FQCN e.g. "android.view.View" */
public String getCanonicalClassName() {
return mCanonicalClassName;
}
/** Returns short class name, e.g. "View" */
public String getShortClassName() {
return mShortClassName;
}
/** Returns the super class. Can be null. */
public ViewClassInfo getSuperClass() {
return mSuperClass;
}
/** Returns a short javadoc */
public String getJavaDoc() {
return mJavaDoc;
}
/** Returns the attributes for this view or view group. Maybe empty but not null. */
public AttributeInfo[] getAttributes() {
return mAttributes;
}
/** Returns the LayoutData info for layout classes. Null for non-layout view classes. */
public LayoutParamsInfo getLayoutData() {
return mLayoutData;
}
/**
* Sets a link on the info of the super class of this View or ViewGroup.
* <p/>
* The super class info must be of the same kind (i.e. group to group or view to view)
* except for the top ViewGroup which links to the View info.
* <p/>
* The super class cannot be null except for the top View info.
*/
public void setSuperClass(ViewClassInfo superClass) {
mSuperClass = superClass;
}
/** Sets the javadoc for this View or ViewGroup. */
public void setJavaDoc(String javaDoc) {
mJavaDoc = javaDoc;
}
/** Sets the list of attributes for this View or ViewGroup. */
public void setAttributes(AttributeInfo[] attributes) {
mAttributes = attributes;
}
/**
* Sets the {@link LayoutParamsInfo} for layout classes.
* Does nothing for non-layout view classes.
*/
public void setLayoutParams(LayoutParamsInfo layoutData) {
if (mIsLayout) {
mLayoutData = layoutData;
}
}
}

View File

@@ -0,0 +1,767 @@
/*
* 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.editors;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider;
import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor;
import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor;
import com.android.ide.eclipse.editors.descriptors.TextValueDescriptor;
import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor;
import com.android.ide.eclipse.editors.uimodel.UiAttributeNode;
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.editors.uimodel.UiFlagAttributeNode;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.regex.Pattern;
/**
* Content Assist Processor for Android XML files
*/
public abstract class AndroidContentAssist implements IContentAssistProcessor {
/** Regexp to detect a full attribute after an element tag.
* <pre>Syntax:
* name = "..." quoted string with all but < and "
* or:
* name = '...' quoted string with all but < and '
* </pre>
*/
private static Pattern sFirstAttribute = Pattern.compile(
"^ *[a-zA-Z_:]+ *= *(?:\"[^<\"]*\"|'[^<']*')"); //$NON-NLS-1$
/** Regexp to detect an element tag name */
private static Pattern sFirstElementWord = Pattern.compile("^[a-zA-Z0-9_:]+"); //$NON-NLS-1$
/** Regexp to detect whitespace */
private static Pattern sWhitespace = Pattern.compile("\\s+"); //$NON-NLS-1$
protected final static String ROOT_ELEMENT = "";
/** Descriptor of the root of the XML hierarchy. This a "fake" ElementDescriptor which
* is used to list all the possible roots given by actual implementations.
* DO NOT USE DIRECTLY. Call {@link #getRootDescriptor()} instead. */
private ElementDescriptor mRootDescriptor;
private final int mDescriptorId;
private AndroidEditor mEditor;
/**
* Constructor for AndroidContentAssist
* @param rootElementDescriptors The valid root elements of the XML hierarchy
*/
public AndroidContentAssist(int descriptorId) {
mDescriptorId = descriptorId;
}
/**
* Returns a list of completion proposals based on the
* specified location within the document that corresponds
* to the current cursor position within the text viewer.
*
* @param viewer the viewer whose document is used to compute the proposals
* @param offset an offset within the document for which completions should be computed
* @return an array of completion proposals or <code>null</code> if no proposals are possible
*
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int)
*/
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
if (mEditor == null) {
mEditor = getAndroidEditor(viewer);
}
UiElementNode rootUiNode = mEditor.getUiRootNode();
Object[] choices = null; /* An array of ElementDescriptor, or AttributeDescriptor
or String or null */
String parent = ""; //$NON-NLS-1$
String wordPrefix = extractElementPrefix(viewer, offset);
char needTag = 0;
boolean isElement = false;
boolean isAttribute = false;
Node currentNode = getNode(viewer, offset);
if (currentNode == null)
return null;
// check to see if we can find a UiElementNode matching this XML node
UiElementNode currentUiNode =
rootUiNode == null ? null : rootUiNode.findXmlNode(currentNode);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
parent = currentNode.getNodeName();
if (wordPrefix.equals(parent)) {
// We are still editing the element's tag name, not the attributes
// (the element's tag name may not even be complete)
isElement = true;
choices = getChoicesForElement(parent, currentNode);
} else {
// We're not editing the current node name, so we might be editing its
// attributes instead...
isAttribute = true;
AttribInfo info = parseAttributeInfo(viewer, offset);
if (info != null) {
// We're editing attributes in an element node (either the attributes' names
// or their values).
choices = getChoicesForAttribute(parent, currentNode, currentUiNode, info);
if (info.correctedPrefix != null) {
wordPrefix = info.correctedPrefix;
}
needTag = info.needTag;
}
}
} else if (currentNode.getNodeType() == Node.TEXT_NODE) {
isElement = true;
// Examine the parent of the text node.
choices = getChoicesForTextNode(currentNode);
}
// Abort if we can't recognize the context or there are no completion choices
if (choices == null || choices.length == 0) return null;
if (isElement) {
// If we found some suggestions, do we need to add an opening "<" bracket
// for the element? We don't if the cursor is right after "<" or "</".
// Per XML Spec, there's no whitespace between "<" or "</" and the tag name.
int offset2 = offset - wordPrefix.length() - 1;
int c1 = extractChar(viewer, offset2);
if (!((c1 == '<') || (c1 == '/' && extractChar(viewer, offset2 - 1) == '<'))) {
needTag = '<';
}
}
// get the selection length
int selectionLength = 0;
ISelection selection = viewer.getSelectionProvider().getSelection();
if (selection instanceof TextSelection) {
TextSelection textSelection = (TextSelection)selection;
selectionLength = textSelection.getLength();
}
return computeProposals(offset, currentNode, choices, wordPrefix, needTag,
isAttribute, selectionLength);
}
/**
* Returns the namespace prefix matching the Android Resource URI.
* If no such declaration is found, returns the default "android" prefix.
*
* @param node The current node. Must not be null.
* @param nsUri The namespace URI of which the prefix is to be found,
* e.g. AndroidConstants.NS_RESOURCES
* @return The first prefix declared or the default "android" prefix.
*/
private String lookupNamespacePrefix(Node node, String nsUri) {
// Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java
// The following emulates this:
// String prefix = node.lookupPrefix(AndroidConstants.NS_RESOURCES);
if (XmlnsAttributeDescriptor.XMLNS_URI.equals(nsUri)) {
return "xmlns"; //$NON-NLS-1$
}
HashSet<String> visited = new HashSet<String>();
String prefix = null;
for (; prefix == null &&
node != null &&
node.getNodeType() == Node.ELEMENT_NODE;
node = node.getParentNode()) {
NamedNodeMap attrs = node.getAttributes();
for (int n = attrs.getLength() - 1; n >= 0; --n) {
Node attr = attrs.item(n);
if ("xmlns".equals(attr.getPrefix())) { //$NON-NLS-1$
String uri = attr.getNodeValue();
if (AndroidConstants.NS_RESOURCES.equals(uri)) {
return attr.getLocalName();
}
visited.add(uri);
}
}
}
// Use a sensible default prefix if we can't find one.
// We need to make sure the prefix is not one that was declared in the scope
// visited above.
prefix = AndroidConstants.NS_RESOURCES.equals(nsUri) ? "android" : "ns"; //$NON-NLS-1$ //$NON-NLS-2$
String base = prefix;
for (int i = 1; visited.contains(prefix); i++) {
prefix = base + Integer.toString(i);
}
return prefix;
}
/**
* Gets the choices when the user is editing the name of an XML element.
* <p/>
* The user is editing the name of an element (the "parent").
* Find the grand-parent and if one is found, return its children element list.
* The name which is being edited should be one of those.
* <p/>
* Example: <manifest><applic*cursor* => returns the list of all elements that
* can be found under <manifest>, of which <application> is one of the choices.
*
* @return an ElementDescriptor[] or null if no valid element was found.
*/
private Object[] getChoicesForElement(String parent, Node current_node) {
ElementDescriptor grandparent = null;
if (current_node.getParentNode().getNodeType() == Node.ELEMENT_NODE) {
grandparent = getDescriptor(current_node.getParentNode().getNodeName());
} else if (current_node.getParentNode().getNodeType() == Node.DOCUMENT_NODE) {
grandparent = getRootDescriptor();
}
if (grandparent != null) {
for (ElementDescriptor e : grandparent.getChildren()) {
if (e.getXmlName().startsWith(parent)) {
return grandparent.getChildren();
}
}
}
return null;
}
/**
* Gets the choices when the user is editing an XML attribute.
* <p/>
* In input, attrInfo contains details on the analyzed context, namely whether the
* user is editing an attribute value (isInValue) or an attribute name.
* <p/>
* In output, attrInfo also contains two possible new values (this is a hack to circumvent
* the lack of out-parameters in Java):
* - AttribInfo.correctedPrefix if the user has been editing an attribute value and it has
* been detected that what the user typed is different from what extractElementPrefix()
* predicted. This happens because extractElementPrefix() stops when a character that
* cannot be an element name appears whereas parseAttributeInfo() uses a grammar more
* lenient as suitable for attribute values.
* - AttribInfo.needTag will be non-zero if we find that the attribute completion proposal
* must be double-quoted.
* @param currentUiNode
*
* @return an AttributeDescriptor[] if the user is editing an attribute name.
* a String[] if the user is editing an attribute value with some known values,
* or null if nothing is known about the context.
*/
private Object[] getChoicesForAttribute(String parent,
Node currentNode, UiElementNode currentUiNode, AttribInfo attrInfo) {
Object[] choices = null;
if (attrInfo.isInValue) {
// Editing an attribute's value... Get the attribute name and then the
// possible choice for the tuple(parent,attribute)
String value = attrInfo.value;
if (value.startsWith("'") || value.startsWith("\"")) { //$NON-NLS-1$ //$NON-NLS-2$
value = value.substring(1);
// The prefix that was found at the beginning only scan for characters
// valid of tag name. We now know the real prefix for this attribute's
// value, which is needed to generate the completion choices below.
attrInfo.correctedPrefix = value;
} else {
attrInfo.needTag = '"';
}
if (currentUiNode != null) {
// look for an UI attribute matching the current attribute name
String attrName = attrInfo.name;
UiAttributeNode currAttrNode = null;
for (UiAttributeNode attrNode : currentUiNode.getUiAttributes()) {
if (attrNode.getDescriptor().getXmlLocalName().equals(attrName)) {
currAttrNode = attrNode;
break;
}
}
if (currAttrNode != null) {
choices = currAttrNode.getPossibleValues();
if (currAttrNode instanceof UiFlagAttributeNode) {
// A "flag" can consist of several values separated by "or" (|).
// If the correct prefix contains such a pipe character, we change
// it so that only the currently edited value is completed.
int pos = value.indexOf('|');
if (pos >= 0) {
attrInfo.correctedPrefix = value = value.substring(pos + 1);
attrInfo.needTag = 0;
}
}
}
}
if (choices == null) {
// fallback on the older descriptor-only based lookup.
// in order to properly handle the special case of the name attribute in
// the action tag, we need the grandparent of the action node, to know
// what type of actions we need.
// e.g. activity -> intent-filter -> action[@name]
String greatGrandParentName = null;
Node grandParent = currentNode.getParentNode();
if (grandParent != null) {
Node greatGrandParent = grandParent.getParentNode();
if (greatGrandParent != null) {
greatGrandParentName = greatGrandParent.getLocalName();
}
}
AndroidTargetData data = mEditor.getTargetData();
if (data != null) {
choices = data.getAttributeValues(parent, attrInfo.name, greatGrandParentName);
}
}
} else {
// Editing an attribute's name... Get attributes valid for the parent node.
if (currentUiNode != null) {
choices = currentUiNode.getAttributeDescriptors();
} else {
ElementDescriptor parent_desc = getDescriptor(parent);
choices = parent_desc.getAttributes();
}
}
return choices;
}
/**
* Gets the choices when the user is editing an XML text node.
* <p/>
* This means the user is editing outside of any XML element or attribute.
* Simply return the list of XML elements that can be present there, based on the
* parent of the current node.
*
* @return An ElementDescriptor[] or null.
*/
private Object[] getChoicesForTextNode(Node currentNode) {
Object[] choices = null;
String parent;
Node parent_node = currentNode.getParentNode();
if (parent_node.getNodeType() == Node.ELEMENT_NODE) {
// We're editing a text node which parent is an element node. Limit
// content assist to elements valid for the parent.
parent = parent_node.getNodeName();
ElementDescriptor desc = getDescriptor(parent);
if (desc != null) {
choices = desc.getChildren();
}
} else if (parent_node.getNodeType() == Node.DOCUMENT_NODE) {
// We're editing a text node at the first level (i.e. root node).
// Limit content assist to the only valid root elements.
choices = getRootDescriptor().getChildren();
}
return choices;
}
/**
* Given a list of choices found, generates the proposals to be displayed to the user.
* <p/>
* Choices is an object array. Items of the array can be:
* - ElementDescriptor: a possible element descriptor which XML name should be completed.
* - AttributeDescriptor: a possible attribute descriptor which XML name should be completed.
* - String: string values to display as-is to the user. Typically those are possible
* values for a given attribute.
*
* @return The ICompletionProposal[] to display to the user.
*/
private ICompletionProposal[] computeProposals(int offset, Node currentNode,
Object[] choices, String wordPrefix, char need_tag,
boolean is_attribute, int selectionLength) {
ArrayList<CompletionProposal> proposals = new ArrayList<CompletionProposal>();
HashMap<String, String> nsUriMap = new HashMap<String, String>();
for (Object choice : choices) {
String keyword = null;
String nsPrefix = null;
Image icon = null;
String tooltip = null;
if (choice instanceof ElementDescriptor) {
keyword = ((ElementDescriptor)choice).getXmlName();
icon = ((ElementDescriptor)choice).getIcon();
tooltip = DescriptorsUtils.formatTooltip(((ElementDescriptor)choice).getTooltip());
} else if (choice instanceof TextValueDescriptor) {
continue; // Value nodes are not part of the completion choices
} else if (choice instanceof SeparatorAttributeDescriptor) {
continue; // not real attribute descriptors
} else if (choice instanceof AttributeDescriptor) {
keyword = ((AttributeDescriptor)choice).getXmlLocalName();
icon = ((AttributeDescriptor)choice).getIcon();
if (choice instanceof TextAttributeDescriptor) {
tooltip = ((TextAttributeDescriptor) choice).getTooltip();
}
String nsUri = ((AttributeDescriptor)choice).getNamespaceUri();
nsPrefix = nsUriMap.get(nsUri);
if (nsPrefix == null) {
nsPrefix = lookupNamespacePrefix(currentNode, nsUri);
nsUriMap.put(nsUri, nsPrefix);
}
if (nsPrefix != null) {
nsPrefix += ":"; //$NON-NLS-1$
}
} else if (choice instanceof String) {
keyword = (String) choice;
} else {
continue; // discard unknown choice
}
if (keyword.startsWith(wordPrefix) ||
(nsPrefix != null && keyword.startsWith(nsPrefix))) {
if (nsPrefix != null) {
keyword = nsPrefix + keyword;
}
String end_tag = ""; //$NON-NLS-1$
if (need_tag != 0) {
if (need_tag == '"') {
keyword = need_tag + keyword;
end_tag = String.valueOf(need_tag);
} else if (need_tag == '<') {
if (elementCanHaveChildren(choice)) {
end_tag = String.format("></%1$s>", keyword); //$NON-NLS-1$
keyword = need_tag + keyword;
} else {
keyword = need_tag + keyword;
end_tag = "/>"; //$NON-NLS-1$
}
}
}
CompletionProposal proposal = new CompletionProposal(
keyword + end_tag, // String replacementString
offset - wordPrefix.length(), // int replacementOffset
wordPrefix.length() + selectionLength, // int replacementLength
keyword.length(), // int cursorPosition (rel. to rplcmntOffset)
icon, // Image image
null, // String displayString
null, // IContextInformation contextInformation
tooltip // String additionalProposalInfo
);
proposals.add(proposal);
}
}
return proposals.toArray(new ICompletionProposal[proposals.size()]);
}
/**
* Indicates whether this descriptor describes an element that can potentially
* have children (either sub-elements or text value). If an element can have children,
* we want to explicitly write an opening and a separate closing tag.
* <p/>
* Elements can have children if the descriptor has children element descriptors
* or if one of the attributes is a TextValueDescriptor.
*
* @param descriptor An ElementDescriptor or an AttributeDescriptor
* @return True if the descriptor is an ElementDescriptor that can have children or a text value
*/
private boolean elementCanHaveChildren(Object descriptor) {
if (descriptor instanceof ElementDescriptor) {
ElementDescriptor desc = (ElementDescriptor) descriptor;
if (desc.hasChildren()) {
return true;
}
for (AttributeDescriptor attr_desc : desc.getAttributes()) {
if (attr_desc instanceof TextValueDescriptor) {
return true;
}
}
}
return false;
}
/**
* Returns the element descriptor matching a given XML node name or null if it can't be
* found.
* <p/>
* This is simplistic; ideally we should consider the parent's chain to make sure we
* can differentiate between different hierarchy trees. Right now the first match found
* is returned.
*/
private ElementDescriptor getDescriptor(String nodeName) {
return getRootDescriptor().findChildrenDescriptor(nodeName, true /* recursive */);
}
public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
return null;
}
/**
* Returns the characters which when entered by the user should
* automatically trigger the presentation of possible completions.
*
* In our case, we auto-activate on opening tags and attributes namespace.
*
* @return the auto activation characters for completion proposal or <code>null</code>
* if no auto activation is desired
*/
public char[] getCompletionProposalAutoActivationCharacters() {
return new char[]{ '<', ':', '=' };
}
public char[] getContextInformationAutoActivationCharacters() {
return null;
}
public IContextInformationValidator getContextInformationValidator() {
return null;
}
public String getErrorMessage() {
return null;
}
/**
* Heuristically extracts the prefix used for determining template relevance
* from the viewer's document. The default implementation returns the String from
* offset backwards that forms a potential XML element name.
*
* Code extracted from org.eclipse.jface.text.templatesTemplateCompletionProcessor
* and adapted to our needs.
*
* @param viewer the viewer
* @param offset offset into document
* @return the prefix to consider
*/
protected String extractElementPrefix(ITextViewer viewer, int offset) {
int i = offset;
IDocument document = viewer.getDocument();
if (i > document.getLength()) return ""; //$NON-NLS-1$
try {
for (; i > 0; --i) {
char ch = document.getChar(i - 1);
// accepted characters are a-z and : (for attributes' namespace)
if (ch != ':' && (ch < 'a' || ch > 'z')) break;
}
return document.get(i, offset - i);
} catch (BadLocationException e) {
return ""; //$NON-NLS-1$
}
}
/**
* Extracts the character at the given offset.
* Returns 0 if the offset is invalid.
*/
protected char extractChar(ITextViewer viewer, int offset) {
IDocument document = viewer.getDocument();
if (offset > document.getLength()) return 0;
try {
return document.getChar(offset);
} catch (BadLocationException e) {
return 0;
}
}
/**
* Information about the current edit of an attribute as reported by parseAttributeInfo.
*/
private class AttribInfo {
/** True if the cursor is located in an attribute's value, false if in an attribute name */
public boolean isInValue = false;
/** The attribute name. Null when not set. */
public String name = null;
/** The attribute value. Null when not set. The value *may* start with a quote
* (' or "), in which case we know we don't need to quote the string for the user */
public String value = null;
/** String typed by the user so far (i.e. right before requesting code completion),
* which will be corrected if we find a possible completion for an attribute value.
* See the long comment in getChoicesForAttribute(). */
public String correctedPrefix = null;
/** Non-zero if an attribute value need a start/end tag (i.e. quotes or brackets) */
public char needTag = 0;
}
/**
* Try to guess if the cursor is editing an element's name or an attribute following an
* element. If it's an attribute, try to find if an attribute name is being defined or
* its value.
* <br/>
* This is currently *only* called when we know the cursor is after a complete element
* tag name, so it should never return null.
* <br/>
* Reference for XML syntax: http://www.w3.org/TR/2006/REC-xml-20060816/#sec-starttags
* <br/>
* @return An AttribInfo describing which attribute is being edited or null if the cursor is
* not editing an attribute (in which case it must be an element's name).
*/
private AttribInfo parseAttributeInfo(ITextViewer viewer, int offset) {
AttribInfo info = new AttribInfo();
IDocument document = viewer.getDocument();
int n = document.getLength();
if (offset <= n) {
try {
n = offset;
for (;offset > 0; --offset) {
char ch = document.getChar(offset - 1);
if (ch == '<') break;
}
// text will contain the full string of the current element,
// i.e. whatever is after the "<" to the current cursor
String text = document.get(offset, n - offset);
// Normalize whitespace to single spaces
text = sWhitespace.matcher(text).replaceAll(" "); //$NON-NLS-1$
// Remove the leading element name. By spec, it must be after the < without
// any whitespace. If there's nothing left, no attribute has been defined yet.
// Be sure to keep any whitespace after the initial word if any, as it matters.
text = sFirstElementWord.matcher(text).replaceFirst(""); //$NON-NLS-1$
// There MUST be space after the element name. If not, the cursor is still
// defining the element name.
if (!text.startsWith(" ")) { //$NON-NLS-1$
return null;
}
// Remove full attributes:
// Syntax:
// name = "..." quoted string with all but < and "
// or:
// name = '...' quoted string with all but < and '
String temp;
do {
temp = text;
text = sFirstAttribute.matcher(temp).replaceFirst(""); //$NON-NLS-1$
} while(!temp.equals(text));
// Now we're left with 3 cases:
// - nothing: either there is no attribute definition or the cursor located after
// a completed attribute definition.
// - a string with no =: the user is writing an attribute name. This case can be
// merged with the previous one.
// - string with an = sign, optionally followed by a quote (' or "): the user is
// writing the value of the attribute.
int pos_equal = text.indexOf('=');
if (pos_equal == -1) {
info.isInValue = false;
info.name = text.trim();
} else {
info.isInValue = true;
info.name = text.substring(0, pos_equal).trim();
info.value = text.substring(pos_equal + 1).trim();
}
return info;
} catch (BadLocationException e) {
// pass
}
}
return null;
}
/**
* Returns the XML DOM node corresponding to the given offset of the given document.
*/
protected Node getNode(ITextViewer viewer, int offset) {
Node node = null;
try {
IModelManager mm = StructuredModelManager.getModelManager();
if (mm != null) {
IStructuredModel model = mm.getExistingModelForRead(viewer.getDocument());
if (model != null) {
for(; offset >= 0 && node == null; --offset) {
node = (Node) model.getIndexedRegion(offset);
}
}
}
} catch (Exception e) {
// Ignore exceptions.
}
return node;
}
/**
* Computes (if needed) and returns the root descriptor.
* @return
*/
private ElementDescriptor getRootDescriptor() {
if (mRootDescriptor == null) {
AndroidTargetData data = mEditor.getTargetData();
if (data != null) {
IDescriptorProvider descriptorProvider = data.getDescriptorProvider(mDescriptorId);
if (descriptorProvider != null) {
mRootDescriptor = new ElementDescriptor("",
descriptorProvider.getRootElementDescriptors());
}
}
}
return mRootDescriptor;
}
/**
* Returns the active {@link AndroidEditor} matching this source viewer.
*/
private AndroidEditor getAndroidEditor(ITextViewer viewer) {
IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (wwin != null) {
IWorkbenchPage page = wwin.getActivePage();
if (page != null) {
IEditorPart editor = page.getActiveEditor();
if (editor instanceof AndroidEditor) {
ISourceViewer ssviewer = ((AndroidEditor) editor).getStructuredSourceViewer();
if (ssviewer == viewer) {
return (AndroidEditor) editor;
}
}
}
}
return null;
}
}

View File

@@ -0,0 +1,820 @@
/*
* 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.editors;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
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.jface.action.IAction;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.editor.IFormPage;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.events.IHyperlinkListener;
import org.eclipse.ui.forms.widgets.FormText;
import org.eclipse.ui.internal.browser.WorkbenchBrowserSupport;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.MultiPageEditorPart;
import org.eclipse.ui.part.WorkbenchPart;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelStateListener;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.ui.StructuredTextEditor;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.w3c.dom.Document;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Multi-page form editor for Android XML files.
* <p/>
* It is designed to work with a {@link StructuredTextEditor} that will display an XML file.
* <br/>
* Derived classes must implement createFormPages to create the forms before the
* source editor. This can be a no-op if desired.
*/
public abstract class AndroidEditor extends FormEditor implements IResourceChangeListener {
/** Preference name for the current page of this file */
private static final String PREF_CURRENT_PAGE = "_current_page";
/** Id string used to create the Android SDK browser */
private static String BROWSER_ID = "android"; // $NON-NLS-1$
/** Page id of the XML source editor, used for switching tabs programmatically */
public final static String TEXT_EDITOR_ID = "editor_part"; //$NON-NLS-1$
/** Width hint for text fields. Helps the grid layout resize properly on smaller screens */
public static final int TEXT_WIDTH_HINT = 50;
/** Page index of the text editor (always the last page) */
private int mTextPageIndex;
/** The text editor */
private StructuredTextEditor mTextEditor;
/** Listener for the XML model from the StructuredEditor */
private XmlModelStateListener mXmlModelStateListener;
/** Listener to update the root node if the resource framework changes */
private Runnable mResourceRefreshListener;
/**
* Creates a form editor.
*/
public AndroidEditor() {
super();
ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
mResourceRefreshListener = new Runnable() {
public void run() {
commitPages(false /* onSave */);
// recreate the ui root node always
initUiRootNode(true /*force*/);
}
};
AdtPlugin.getDefault().addResourceChangedListener(mResourceRefreshListener);
}
// ---- Abstract Methods ----
/**
* Returns the root node of the UI element hierarchy manipulated by the current
* UI node editor.
*/
abstract public UiElementNode getUiRootNode();
/**
* Creates the various form pages.
* <p/>
* Derived classes must implement this to add their own specific tabs.
*/
abstract protected void createFormPages();
/**
* Creates the initial UI Root Node, including the known mandatory elements.
* @param force if true, a new UiManifestNode is recreated even if it already exists.
*/
abstract protected void initUiRootNode(boolean force);
/**
* Subclasses should override this method to process the new XML Model, which XML
* root node is given.
*
* The base implementation is empty.
*
* @param xml_doc The XML document, if available, or null if none exists.
*/
protected void xmlModelChanged(Document xml_doc) {
// pass
}
// ---- Base Class Overrides, Interfaces Implemented ----
/**
* Creates the pages of the multi-page editor.
*/
@Override
protected void addPages() {
createAndroidPages();
selectDefaultPage(null /* defaultPageId */);
}
/**
* Creates the page for the Android Editors
*/
protected void createAndroidPages() {
createFormPages();
createTextEditor();
createUndoRedoActions();
}
/**
* Creates undo redo actions for the editor site (so that it works for any page of this
* multi-page editor) by re-using the actions defined by the {@link StructuredTextEditor}
* (aka the XML text editor.)
*/
private void createUndoRedoActions() {
IActionBars bars = getEditorSite().getActionBars();
if (bars != null) {
IAction action = mTextEditor.getAction(ActionFactory.UNDO.getId());
bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), action);
action = mTextEditor.getAction(ActionFactory.REDO.getId());
bars.setGlobalActionHandler(ActionFactory.REDO.getId(), action);
bars.updateActionBars();
}
}
/**
* Selects the default active page.
* @param defaultPageId the id of the page to show. If <code>null</code> the editor attempts to
* find the default page in the properties of the {@link IResource} object being edited.
*/
protected void selectDefaultPage(String defaultPageId) {
if (defaultPageId == null) {
if (getEditorInput() instanceof IFileEditorInput) {
IFile file = ((IFileEditorInput) getEditorInput()).getFile();
QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID,
getClass().getSimpleName() + PREF_CURRENT_PAGE);
String pageId;
try {
pageId = file.getPersistentProperty(qname);
if (pageId != null) {
defaultPageId = pageId;
}
} catch (CoreException e) {
// ignored
}
}
}
if (defaultPageId != null) {
try {
setActivePage(Integer.parseInt(defaultPageId));
} catch (Exception e) {
// We can get NumberFormatException from parseInt but also
// AssertionError from setActivePage when the index is out of bounds.
// Generally speaking we just want to ignore any exception and fall back on the
// first page rather than crash the editor load. Logging the error is enough.
AdtPlugin.log(e, "Selecting page '%s' in AndroidEditor failed", defaultPageId);
}
}
}
/**
* Removes all the pages from the editor.
*/
protected void removePages() {
int count = getPageCount();
for (int i = count - 1 ; i >= 0 ; i--) {
removePage(i);
}
}
/**
* Overrides the parent's setActivePage to be able to switch to the xml editor.
*
* If the special pageId TEXT_EDITOR_ID is given, switches to the mTextPageIndex page.
* This is needed because the editor doesn't actually derive from IFormPage and thus
* doesn't have the get-by-page-id method. In this case, the method returns null since
* IEditorPart does not implement IFormPage.
*/
@Override
public IFormPage setActivePage(String pageId) {
if (pageId.equals(TEXT_EDITOR_ID)) {
super.setActivePage(mTextPageIndex);
return null;
} else {
return super.setActivePage(pageId);
}
}
/**
* Notifies this multi-page editor that the page with the given id has been
* activated. This method is called when the user selects a different tab.
*
* @see MultiPageEditorPart#pageChange(int)
*/
@Override
protected void pageChange(int newPageIndex) {
super.pageChange(newPageIndex);
if (getEditorInput() instanceof IFileEditorInput) {
IFile file = ((IFileEditorInput) getEditorInput()).getFile();
QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID,
getClass().getSimpleName() + PREF_CURRENT_PAGE);
try {
file.setPersistentProperty(qname, Integer.toString(newPageIndex));
} catch (CoreException e) {
// ignore
}
}
}
/**
* Notifies this listener that some resource changes
* are happening, or have already happened.
*
* Closes all project files on project close.
* @see IResourceChangeListener
*/
public void resourceChanged(final IResourceChangeEvent event) {
if (event.getType() == IResourceChangeEvent.PRE_CLOSE) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
IWorkbenchPage[] pages = getSite().getWorkbenchWindow()
.getPages();
for (int i = 0; i < pages.length; i++) {
if (((FileEditorInput)mTextEditor.getEditorInput())
.getFile().getProject().equals(
event.getResource())) {
IEditorPart editorPart = pages[i].findEditor(mTextEditor
.getEditorInput());
pages[i].closeEditor(editorPart, true);
}
}
}
});
}
}
/**
* Initializes the editor part with a site and input.
* <p/>
* Checks that the input is an instance of {@link IFileEditorInput}.
*
* @see FormEditor
*/
@Override
public void init(IEditorSite site, IEditorInput editorInput) throws PartInitException {
if (!(editorInput instanceof IFileEditorInput))
throw new PartInitException("Invalid Input: Must be IFileEditorInput");
super.init(site, editorInput);
}
/**
* Removes attached listeners.
*
* @see WorkbenchPart
*/
@Override
public void dispose() {
IStructuredModel xml_model = getModelForRead();
if (xml_model != null) {
try {
if (mXmlModelStateListener != null) {
xml_model.removeModelStateListener(mXmlModelStateListener);
}
} finally {
xml_model.releaseFromRead();
}
}
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
if (mResourceRefreshListener != null) {
AdtPlugin.getDefault().removeResourceChangedListener(mResourceRefreshListener);
mResourceRefreshListener = null;
}
super.dispose();
}
/**
* Commit all dirty pages then saves the contents of the text editor.
* <p/>
* This works by committing all data to the XML model and then
* asking the Structured XML Editor to save the XML.
*
* @see IEditorPart
*/
@Override
public void doSave(IProgressMonitor monitor) {
commitPages(true /* onSave */);
// The actual "save" operation is done by the Structured XML Editor
getEditor(mTextPageIndex).doSave(monitor);
}
/* (non-Javadoc)
* Saves the contents of this editor to another object.
* <p>
* Subclasses must override this method to implement the open-save-close lifecycle
* for an editor. For greater details, see <code>IEditorPart</code>
* </p>
*
* @see IEditorPart
*/
@Override
public void doSaveAs() {
commitPages(true /* onSave */);
IEditorPart editor = getEditor(mTextPageIndex);
editor.doSaveAs();
setPageText(mTextPageIndex, editor.getTitle());
setInput(editor.getEditorInput());
}
/**
* Commits all dirty pages in the editor. This method should
* be called as a first step of a 'save' operation.
* <p/>
* This is the same implementation as in {@link FormEditor}
* except it fixes two bugs: a cast to IFormPage is done
* from page.get(i) <em>before</em> being tested with instanceof.
* Another bug is that the last page might be a null pointer.
* <p/>
* The incorrect casting makes the original implementation crash due
* to our {@link StructuredTextEditor} not being an {@link IFormPage}
* so we have to override and duplicate to fix it.
*
* @param onSave <code>true</code> if commit is performed as part
* of the 'save' operation, <code>false</code> otherwise.
* @since 3.3
*/
@Override
public void commitPages(boolean onSave) {
if (pages != null) {
for (int i = 0; i < pages.size(); i++) {
Object page = pages.get(i);
if (page != null && page instanceof IFormPage) {
IFormPage form_page = (IFormPage) page;
IManagedForm managed_form = form_page.getManagedForm();
if (managed_form != null && managed_form.isDirty()) {
managed_form.commit(onSave);
}
}
}
}
}
/* (non-Javadoc)
* Returns whether the "save as" operation is supported by this editor.
* <p>
* Subclasses must override this method to implement the open-save-close lifecycle
* for an editor. For greater details, see <code>IEditorPart</code>
* </p>
*
* @see IEditorPart
*/
@Override
public boolean isSaveAsAllowed() {
return false;
}
// ---- Local methods ----
/**
* Helper method that creates a new hyper-link Listener.
* Used by derived classes which need active links in {@link FormText}.
* <p/>
* This link listener handles two kinds of URLs:
* <ul>
* <li> Links starting with "http" are simply sent to a local browser.
* <li> Links starting with "file:/" are simply sent to a local browser.
* <li> Links starting with "page:" are expected to be an editor page id to switch to.
* <li> Other links are ignored.
* </ul>
*
* @return A new hyper-link listener for FormText to use.
*/
public final IHyperlinkListener createHyperlinkListener() {
return new HyperlinkAdapter() {
/**
* Switch to the page corresponding to the link that has just been clicked.
* For this purpose, the HREF of the &lt;a&gt; tags above is the page ID to switch to.
*/
@Override
public void linkActivated(HyperlinkEvent e) {
super.linkActivated(e);
String link = e.data.toString();
if (link.startsWith("http") || //$NON-NLS-1$
link.startsWith("file:/")) { //$NON-NLS-1$
openLinkInBrowser(link);
} else if (link.startsWith("page:")) { //$NON-NLS-1$
// Switch to an internal page
setActivePage(link.substring(5 /* strlen("page:") */));
}
}
};
}
/**
* Open the http link into a browser
*
* @param link The URL to open in a browser
*/
private void openLinkInBrowser(String link) {
try {
IWorkbenchBrowserSupport wbs = WorkbenchBrowserSupport.getInstance();
wbs.createBrowser(BROWSER_ID).openURL(new URL(link));
} catch (PartInitException e1) {
// pass
} catch (MalformedURLException e1) {
// pass
}
}
/**
* Creates the XML source editor.
* <p/>
* Memorizes the index page of the source editor (it's always the last page, but the number
* of pages before can change.)
* <br/>
* Retrieves the underlying XML model from the StructuredEditor and attaches a listener to it.
* Finally triggers modelChanged() on the model listener -- derived classes can use this
* to initialize the model the first time.
* <p/>
* Called only once <em>after</em> createFormPages.
*/
private void createTextEditor() {
try {
mTextEditor = new StructuredTextEditor();
int index = addPage(mTextEditor, getEditorInput());
mTextPageIndex = index;
setPageText(index, mTextEditor.getTitle());
if (!(mTextEditor.getTextViewer().getDocument() instanceof IStructuredDocument)) {
Status status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
"Error opening the Android XML editor. Is the document an XML file?");
throw new RuntimeException("Android XML Editor Error", new CoreException(status));
}
IStructuredModel xml_model = getModelForRead();
if (xml_model != null) {
try {
mXmlModelStateListener = new XmlModelStateListener();
xml_model.addModelStateListener(mXmlModelStateListener);
mXmlModelStateListener.modelChanged(xml_model);
} catch (Exception e) {
AdtPlugin.log(e, "Error while loading editor"); //$NON-NLS-1$
} finally {
xml_model.releaseFromRead();
}
}
} catch (PartInitException e) {
ErrorDialog.openError(getSite().getShell(),
"Android XML Editor Error", null, e.getStatus());
}
}
/**
* Returns the ISourceViewer associated with the Structured Text editor.
*/
public final ISourceViewer getStructuredSourceViewer() {
if (mTextEditor != null) {
// We can't access mEditor.getSourceViewer() because it is protected,
// however getTextViewer simply returns the SourceViewer casted, so we
// can use it instead.
return mTextEditor.getTextViewer();
}
return null;
}
/**
* Returns the {@link IStructuredDocument} used by the StructuredTextEditor (aka Source
* Editor) or null if not available.
*/
public final IStructuredDocument getStructuredDocument() {
if (mTextEditor != null && mTextEditor.getTextViewer() != null) {
return (IStructuredDocument) mTextEditor.getTextViewer().getDocument();
}
return null;
}
/**
* Returns a version of the model that has been shared for read.
* <p/>
* Callers <em>must</em> call model.releaseFromRead() when done, typically
* in a try..finally clause.
*
* @return The model for the XML document or null if cannot be obtained from the editor
*/
public final IStructuredModel getModelForRead() {
IStructuredDocument document = getStructuredDocument();
if (document != null) {
IModelManager mm = StructuredModelManager.getModelManager();
if (mm != null) {
return mm.getModelForRead(document);
}
}
return null;
}
/**
* Returns a version of the model that has been shared for edit.
* <p/>
* Callers <em>must</em> call model.releaseFromEdit() when done, typically
* in a try..finally clause.
*
* @return The model for the XML document or null if cannot be obtained from the editor
*/
public final IStructuredModel getModelForEdit() {
IStructuredDocument document = getStructuredDocument();
if (document != null) {
IModelManager mm = StructuredModelManager.getModelManager();
if (mm != null) {
return mm.getModelForEdit(document);
}
}
return null;
}
/**
* Helper class to perform edits on the XML model whilst making sure the
* model has been prepared to be changed.
* <p/>
* It first gets a model for edition using {@link #getModelForEdit()},
* then calls {@link IStructuredModel#aboutToChangeModel()},
* then performs the requested action
* and finally calls {@link IStructuredModel#changedModel()}
* and {@link IStructuredModel#releaseFromEdit()}.
* <p/>
* The method is synchronous. As soon as the {@link IStructuredModel#changedModel()} method
* is called, XML model listeners will be triggered.
*
* @param edit_action Something that will change the XML.
*/
public final void editXmlModel(Runnable edit_action) {
IStructuredModel model = getModelForEdit();
try {
model.aboutToChangeModel();
edit_action.run();
} finally {
// Notify the model we're done modifying it. This must *always* be executed.
model.changedModel();
model.releaseFromEdit();
}
}
/**
* Starts an "undo recording" session. This is managed by the underlying undo manager
* associated to the structured XML model.
* <p/>
* There <em>must</em> be a corresponding call to {@link #endUndoRecording()}.
* <p/>
* beginUndoRecording/endUndoRecording calls can be nested (inner calls are ignored, only one
* undo operation is recorded.)
*
* @param label The label for the undo operation. Can be null but we should really try to put
* something meaningful if possible.
* @return True if the undo recording actually started, false if any kind of error occured.
* {@link #endUndoRecording()} should only be called if True is returned.
*/
private final boolean beginUndoRecording(String label) {
IStructuredDocument document = getStructuredDocument();
if (document != null) {
IModelManager mm = StructuredModelManager.getModelManager();
if (mm != null) {
IStructuredModel model = mm.getModelForEdit(document);
if (model != null) {
model.beginRecording(this, label);
return true;
}
}
}
return false;
}
/**
* Ends an "undo recording" session.
* <p/>
* This is the counterpart call to {@link #beginUndoRecording(String)} and should only be
* used if the initial call returned true.
*/
private final void endUndoRecording() {
IStructuredDocument document = getStructuredDocument();
if (document != null) {
IModelManager mm = StructuredModelManager.getModelManager();
if (mm != null) {
IStructuredModel model = mm.getModelForEdit(document);
if (model != null) {
model.endRecording(this);
}
}
}
}
/**
* Creates an "undo recording" session by calling the undoableAction runnable
* using {@link #beginUndoRecording(String)} and {@link #endUndoRecording()}.
* <p>
* You can nest several calls to {@link #wrapUndoRecording(String, Runnable)}, only one
* recording session will be created.
*
* @param label The label for the undo operation. Can be null. Ideally we should really try
* to put something meaningful if possible.
*/
public void wrapUndoRecording(String label, Runnable undoableAction) {
boolean recording = false;
try {
recording = beginUndoRecording(label);
undoableAction.run();
} finally {
if (recording) {
endUndoRecording();
}
}
}
/**
* Returns the XML {@link Document} or null if we can't get it
*/
protected final Document getXmlDocument(IStructuredModel model) {
if (model == null) {
AdtPlugin.log(IStatus.WARNING, "Android Editor: No XML model for root node."); //$NON-NLS-1$
return null;
}
if (model instanceof IDOMModel) {
IDOMModel dom_model = (IDOMModel) model;
return dom_model.getDocument();
}
return null;
}
/**
* Returns the {@link IProject} for the edited file.
*/
public IProject getProject() {
if (mTextEditor != null) {
IEditorInput input = mTextEditor.getEditorInput();
if (input instanceof FileEditorInput) {
FileEditorInput fileInput = (FileEditorInput)input;
IFile inputFile = fileInput.getFile();
if (inputFile != null) {
return inputFile.getProject();
}
}
}
return null;
}
/**
* Returns the {@link PlatformData} for the edited file.
*/
public AndroidTargetData getTargetData() {
IProject project = getProject();
if (project != null) {
Sdk currentSdk = Sdk.getCurrent();
if (currentSdk != null) {
IAndroidTarget target = currentSdk.getTarget(project);
if (target != null) {
return currentSdk.getTargetData(target);
}
}
}
return null;
}
/**
* Listen to changes in the underlying XML model in the structured editor.
*/
private class XmlModelStateListener implements IModelStateListener {
/**
* A model is about to be changed. This typically is initiated by one
* client of the model, to signal a large change and/or a change to the
* model's ID or base Location. A typical use might be if a client might
* want to suspend processing until all changes have been made.
* <p/>
* This AndroidEditor implementation of IModelChangedListener is empty.
*/
public void modelAboutToBeChanged(IStructuredModel model) {
// pass
}
/**
* Signals that the changes foretold by modelAboutToBeChanged have been
* made. A typical use might be to refresh, or to resume processing that
* was suspended as a result of modelAboutToBeChanged.
* <p/>
* This AndroidEditor implementation calls the xmlModelChanged callback.
*/
public void modelChanged(IStructuredModel model) {
xmlModelChanged(getXmlDocument(model));
}
/**
* Notifies that a model's dirty state has changed, and passes that state
* in isDirty. A model becomes dirty when any change is made, and becomes
* not-dirty when the model is saved.
* <p/>
* This AndroidEditor implementation of IModelChangedListener is empty.
*/
public void modelDirtyStateChanged(IStructuredModel model, boolean isDirty) {
// pass
}
/**
* A modelDeleted means the underlying resource has been deleted. The
* model itself is not removed from model management until all have
* released it. Note: baseLocation is not (necessarily) changed in this
* event, but may not be accurate.
* <p/>
* This AndroidEditor implementation of IModelChangedListener is empty.
*/
public void modelResourceDeleted(IStructuredModel model) {
// pass
}
/**
* A model has been renamed or copied (as in saveAs..). In the renamed
* case, the two paramenters are the same instance, and only contain the
* new info for id and base location.
* <p/>
* This AndroidEditor implementation of IModelChangedListener is empty.
*/
public void modelResourceMoved(IStructuredModel oldModel, IStructuredModel newModel) {
// pass
}
/**
* This AndroidEditor implementation of IModelChangedListener is empty.
*/
public void modelAboutToBeReinitialized(IStructuredModel structuredModel) {
// pass
}
/**
* This AndroidEditor implementation of IModelChangedListener is empty.
*/
public void modelReinitialized(IStructuredModel structuredModel) {
// pass
}
}
}

View File

@@ -0,0 +1,116 @@
/*
* 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.editors;
import org.eclipse.jface.text.IAutoEditStrategy;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextHover;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.formatter.IContentFormatter;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.viewers.IInputProvider;
import org.eclipse.wst.sse.core.text.IStructuredPartitions;
import org.eclipse.wst.xml.core.text.IXMLPartitions;
import org.eclipse.wst.xml.ui.StructuredTextViewerConfigurationXML;
import java.util.ArrayList;
/**
* Base Source Viewer Configuration for Android resources.
*/
public class AndroidSourceViewerConfig extends StructuredTextViewerConfigurationXML {
/** Content Assist Processor to use for all handled partitions. */
private IContentAssistProcessor mProcessor;
public AndroidSourceViewerConfig(IContentAssistProcessor processor) {
super();
mProcessor = processor;
}
@Override
public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
return super.getContentAssistant(sourceViewer);
}
/**
* Returns the content assist processors that will be used for content
* assist in the given source viewer and for the given partition type.
*
* @param sourceViewer the source viewer to be configured by this
* configuration
* @param partitionType the partition type for which the content assist
* processors are applicable
* @return IContentAssistProcessors or null if should not be supported
*/
@Override
protected IContentAssistProcessor[] getContentAssistProcessors(
ISourceViewer sourceViewer, String partitionType) {
ArrayList<IContentAssistProcessor> processors = new ArrayList<IContentAssistProcessor>();
if (partitionType == IStructuredPartitions.UNKNOWN_PARTITION ||
partitionType == IStructuredPartitions.DEFAULT_PARTITION ||
partitionType == IXMLPartitions.XML_DEFAULT) {
if (sourceViewer instanceof IInputProvider) {
IInputProvider input = (IInputProvider) sourceViewer;
Object a = input.getInput();
if (a != null)
a.toString();
}
IDocument doc = sourceViewer.getDocument();
if (doc != null)
doc.toString();
processors.add(mProcessor);
}
IContentAssistProcessor[] others = super.getContentAssistProcessors(sourceViewer,
partitionType);
if (others != null && others.length > 0) {
for (IContentAssistProcessor p : others) {
processors.add(p);
}
}
if (processors.size() > 0) {
return processors.toArray(new IContentAssistProcessor[processors.size()]);
} else {
return null;
}
}
@Override
public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) {
// TODO text hover for android xml
return super.getTextHover(sourceViewer, contentType);
}
@Override
public IAutoEditStrategy[] getAutoEditStrategies(
ISourceViewer sourceViewer, String contentType) {
// TODO auto edit strategies for android xml
return super.getAutoEditStrategies(sourceViewer, contentType);
}
@Override
public IContentFormatter getContentFormatter(ISourceViewer sourceViewer) {
// TODO content formatter for android xml
return super.getContentFormatter(sourceViewer);
}
}

View File

@@ -0,0 +1,164 @@
/*
* 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.editors;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
/**
* Quickly parses a (potential) XML file to extract its first element (i.e. the root element)
* and namespace, if any.
* <p/>
* This is used to determine if a file is an XML document that the XmlEditor can process.
* <p/>
* TODO use this to remove the hardcoded "android" namespace prefix limitation.
*/
public final class FirstElementParser {
private static SAXParserFactory sSaxfactory;
/**
* Result from the XML parsing. <br/>
* Contains the name of the root XML element. <br/>
* If an XMLNS URI was specified and found, the XMLNS prefix is recorded. Otherwise it is null.
*/
public static final class Result {
private String mElement;
private String mXmlnsPrefix;
private String mXmlnsUri;
public String getElement() {
return mElement;
}
public String getXmlnsPrefix() {
return mXmlnsPrefix;
}
public String getXmlnsUri() {
return mXmlnsUri;
}
void setElement(String element) {
mElement = element;
}
void setXmlnsPrefix(String xmlnsPrefix) {
mXmlnsPrefix = xmlnsPrefix;
}
void setXmlnsUri(String xmlnsUri) {
mXmlnsUri = xmlnsUri;
}
}
private static class ResultFoundException extends SAXException { }
/**
* Parses the given filename.
*
* @param osFilename The file to parse.
* @param xmlnsUri An optional URL of which we want to know the prefix.
* @return The element details found or null if not found.
*/
public static Result parse(String osFilename, String xmlnsUri) {
if (sSaxfactory == null) {
// TODO just create a single factory in CommonPlugin and reuse it
sSaxfactory = SAXParserFactory.newInstance();
sSaxfactory.setNamespaceAware(true);
}
Result result = new Result();
if (xmlnsUri != null && xmlnsUri.length() > 0) {
result.setXmlnsUri(xmlnsUri);
}
try {
SAXParser parser = sSaxfactory.newSAXParser();
XmlHandler handler = new XmlHandler(result);
parser.parse(new InputSource(new FileReader(osFilename)), handler);
} catch(ResultFoundException e) {
// XML handling was aborted because the required element was found.
// Simply return the result.
return result;
} catch (ParserConfigurationException e) {
} catch (SAXException e) {
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
return null;
}
/**
* Private constructor. Use the static parse() method instead.
*/
private FirstElementParser() {
// pass
}
/**
* A specialized SAX handler that captures the arguments of the very first element
* (i.e. the root element)
*/
private static class XmlHandler extends DefaultHandler {
private final Result mResult;
public XmlHandler(Result result) {
mResult = result;
}
/**
* Processes a namespace prefix mapping.
* I.e. for xmlns:android="some-uri", this received prefix="android" and uri="some-uri".
* <p/>
* The prefix is recorded in the result structure if the URI is the one searched for.
* <p/>
* This event happens <em>before</em> the corresponding startElement event.
*/
@Override
public void startPrefixMapping(String prefix, String uri) {
if (uri.equals(mResult.getXmlnsUri())) {
mResult.setXmlnsPrefix(prefix);
}
}
/**
* Processes a new element start.
* <p/>
* This simply records the element name and abort processing by throwing an exception.
*/
@Override
public void startElement(String uri, String localName, String name, Attributes attributes)
throws SAXException {
mResult.setElement(localName);
throw new ResultFoundException();
}
}
}

View File

@@ -0,0 +1,255 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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.editors;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.common.AndroidConstants;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import java.util.HashMap;
/**
* Factory to generate icons for Android Editors.
* <p/>
* Icons are kept here and reused.
*/
public class IconFactory {
public static final int COLOR_RED = SWT.COLOR_DARK_RED;
public static final int COLOR_GREEN = SWT.COLOR_DARK_GREEN;
public static final int COLOR_BLUE = SWT.COLOR_DARK_BLUE;
public static final int COLOR_DEFAULT = SWT.COLOR_BLACK;
public static final int SHAPE_CIRCLE = 'C';
public static final int SHAPE_RECT = 'R';
public static final int SHAPE_DEFAULT = SHAPE_CIRCLE;
private static IconFactory sInstance;
private HashMap<String, Image> mIconMap = new HashMap<String, Image>();
private HashMap<String, ImageDescriptor> mImageDescMap = new HashMap<String, ImageDescriptor>();
private IconFactory() {
}
public static synchronized IconFactory getInstance() {
if (sInstance == null) {
sInstance = new IconFactory();
}
return sInstance;
}
public void Dispose() {
// Dispose icons
for (Image icon : mIconMap.values()) {
// The map can contain null values
if (icon != null) {
icon.dispose();
}
}
mIconMap.clear();
}
/**
* Returns an Image for a given icon name.
* <p/>
* Callers should not dispose it.
*
* @param osName The leaf name, without the extension, of an existing icon in the
* editor's "icons" directory. If it doesn't exists, a default icon will be
* generated automatically based on the name.
*/
public Image getIcon(String osName) {
return getIcon(osName, COLOR_DEFAULT, SHAPE_DEFAULT);
}
/**
* Returns an Image for a given icon name.
* <p/>
* Callers should not dispose it.
*
* @param osName The leaf name, without the extension, of an existing icon in the
* editor's "icons" directory. If it doesn't exists, a default icon will be
* generated automatically based on the name.
* @param color The color of the text in the automatically generated icons,
* one of COLOR_DEFAULT, COLOR_RED, COLOR_BLUE or COLOR_RED.
* @param shape The shape of the icon in the automatically generated icons,
* one of SHAPE_DEFAULT, SHAPE_CIRCLE or SHAPE_RECT.
*/
public Image getIcon(String osName, int color, int shape) {
String key = Character.toString((char) shape) + Integer.toString(color) + osName;
Image icon = mIconMap.get(key);
if (icon == null && !mIconMap.containsKey(key)) {
ImageDescriptor id = getImageDescriptor(osName, color, shape);
if (id != null) {
icon = id.createImage();
}
// Note that we store null references in the icon map, to avoid looking them
// up every time. If it didn't exist once, it will not exist later.
mIconMap.put(key, icon);
}
return icon;
}
/**
* Returns an ImageDescriptor for a given icon name.
* <p/>
* Callers should not dispose it.
*
* @param osName The leaf name, without the extension, of an existing icon in the
* editor's "icons" directory. If it doesn't exists, a default icon will be
* generated automatically based on the name.
*/
public ImageDescriptor getImageDescriptor(String osName) {
return getImageDescriptor(osName, COLOR_DEFAULT, SHAPE_DEFAULT);
}
/**
* Returns an ImageDescriptor for a given icon name.
* <p/>
* Callers should not dispose it.
*
* @param osName The leaf name, without the extension, of an existing icon in the
* editor's "icons" directory. If it doesn't exists, a default icon will be
* generated automatically based on the name.
* @param color The color of the text in the automatically generated icons.
* one of COLOR_DEFAULT, COLOR_RED, COLOR_BLUE or COLOR_RED.
* @param shape The shape of the icon in the automatically generated icons,
* one of SHAPE_DEFAULT, SHAPE_CIRCLE or SHAPE_RECT.
*/
public ImageDescriptor getImageDescriptor(String osName, int color, int shape) {
String key = Character.toString((char) shape) + Integer.toString(color) + osName;
ImageDescriptor id = mImageDescMap.get(key);
if (id == null && !mImageDescMap.containsKey(key)) {
id = AdtPlugin.imageDescriptorFromPlugin(
AdtPlugin.PLUGIN_ID,
String.format("/icons/%1$s.png", osName)); //$NON-NLS-1$
if (id == null) {
id = new LetterImageDescriptor(osName.charAt(0), color, shape);
}
// Note that we store null references in the icon map, to avoid looking them
// up every time. If it didn't exist once, it will not exist later.
mImageDescMap.put(key, id);
}
return id;
}
/**
* A simple image description that generates a 16x16 image which consists
* of a colored letter inside a black & white circle.
*/
private static class LetterImageDescriptor extends ImageDescriptor {
private final char mLetter;
private final int mColor;
private final int mShape;
public LetterImageDescriptor(char letter, int color, int shape) {
mLetter = letter;
mColor = color;
mShape = shape;
}
@Override
public ImageData getImageData() {
final int SX = 15;
final int SY = 15;
final int RX = 4;
final int RY = 4;
Display display = Display.getCurrent();
if (display == null) {
return null;
}
Image image = new Image(display, SX, SY);
image.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
GC gc = new GC(image);
gc.setAdvanced(true);
gc.setAntialias(SWT.ON);
gc.setTextAntialias(SWT.ON);
gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
if (mShape == SHAPE_CIRCLE) {
gc.fillOval(0, 0, SX - 1, SY - 1);
} else if (mShape == SHAPE_RECT) {
gc.fillRoundRectangle(0, 0, SX - 1, SY - 1, RX, RY);
}
gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
gc.setLineWidth(1);
if (mShape == SHAPE_CIRCLE) {
gc.drawOval(0, 0, SX - 1, SY - 1);
} else if (mShape == SHAPE_RECT) {
gc.drawRoundRectangle(0, 0, SX - 1, SY - 1, RX, RY);
}
// Get a bold version of the default system font, if possible.
Font font = display.getSystemFont();
FontData[] fds = font.getFontData();
fds[0].setStyle(SWT.BOLD);
// use 3/4th of the circle diameter for the font size (in pixels)
// and convert it to "font points" (font points in SWT are hardcoded in an
// arbitrary 72 dpi and then converted in real pixels using whatever is
// indicated by getDPI -- at least that's how it works under Win32).
fds[0].setHeight((int) ((SY + 1) * 3./4. * 72./display.getDPI().y));
// Note: win32 implementation always uses fds[0] so we change just that one.
// getFontData indicates that the array of fd is really an unusual thing for X11.
font = new Font(display, fds);
gc.setFont(font);
gc.setForeground(display.getSystemColor(mColor));
// Text measurement varies so slightly depending on the platform
int ofx = 0;
int ofy = 0;
if (AndroidConstants.CURRENT_PLATFORM == AndroidConstants.PLATFORM_WINDOWS) {
ofx = +1;
ofy = -1;
}
String s = Character.toString(mLetter).toUpperCase();
Point p = gc.textExtent(s);
int tx = (SX + ofx - p.x) / 2;
int ty = (SY + ofy - p.y) / 2;
gc.drawText(s, tx, ty, true /* isTransparent */);
font.dispose();
gc.dispose();
ImageData data = image.getImageData();
image.dispose();
return data;
}
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.editors.descriptors;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.editors.IconFactory;
import com.android.ide.eclipse.editors.uimodel.UiAttributeNode;
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
import org.eclipse.swt.graphics.Image;
/**
* {@link AttributeDescriptor} describes an XML attribute with its XML attribute name.
* <p/>
* An attribute descriptor also knows which UI node should be instantiated to represent
* this particular attribute (e.g. text field, icon chooser, class selector, etc.)
* Some attributes may be hidden and have no user interface at all.
* <p/>
* This is an abstract class. Derived classes must implement data description and return
* the correct UiAttributeNode-derived class.
*/
public abstract class AttributeDescriptor {
private String mXmlLocalName;
private ElementDescriptor mParent;
private final String mNsUri;
/**
* Creates a new {@link AttributeDescriptor}
*
* @param xmlLocalName The XML name of the attribute (case sensitive)
* @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
* See {@link AndroidConstants#NS_RESOURCES} for a common value.
*/
public AttributeDescriptor(String xmlLocalName, String nsUri) {
mXmlLocalName = xmlLocalName;
mNsUri = nsUri;
}
/**
* Returns the XML local name of the attribute (case sensitive)
*/
public final String getXmlLocalName() {
return mXmlLocalName;
}
public final String getNamespaceUri() {
return mNsUri;
}
final void setParent(ElementDescriptor parent) {
mParent = parent;
}
public final ElementDescriptor getParent() {
return mParent;
}
/**
* Returns an optional icon for the attribute.
* <p/>
* By default this tries to return an icon based on the XML name of the attribute.
* If this fails, it tries to return the default Android logo as defined in the
* plugin. If all fails, it returns null.
*
* @return An icon for this element or null.
*/
public Image getIcon() {
IconFactory factory = IconFactory.getInstance();
Image icon;
icon = factory.getIcon(getXmlLocalName(), IconFactory.COLOR_RED, IconFactory.SHAPE_CIRCLE);
return icon != null ? icon : AdtPlugin.getAndroidLogo();
}
/**
* @param uiParent The {@link UiElementNode} parent of this UI attribute.
* @return A new {@link UiAttributeNode} linked to this descriptor or null if this
* attribute has no user interface.
*/
public abstract UiAttributeNode createUiNode(UiElementNode uiParent);
}

View File

@@ -0,0 +1,69 @@
/*
* 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.editors.descriptors;
import com.android.ide.eclipse.editors.uimodel.UiAbstractTextAttributeNode;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.swt.graphics.Image;
/**
* Label provider for {@link UiAbstractTextAttributeNode}.
*/
public class AttributeDescriptorLabelProvider implements ILabelProvider {
private final static AttributeDescriptorLabelProvider sThis =
new AttributeDescriptorLabelProvider();
public static ILabelProvider getProvider() {
return sThis;
}
public Image getImage(Object element) {
return null;
}
public String getText(Object element) {
if (element instanceof UiAbstractTextAttributeNode) {
return ((UiAbstractTextAttributeNode)element).getCurrentValue();
}
return null;
}
public void addListener(ILabelProviderListener listener) {
// TODO Auto-generated method stub
}
public void dispose() {
// TODO Auto-generated method stub
}
public boolean isLabelProperty(Object element, String property) {
// TODO Auto-generated method stub
return false;
}
public void removeListener(ILabelProviderListener listener) {
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.editors.descriptors;
import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode;
/**
* Describes a text attribute that can only contain boolean values.
* It is displayed by a {@link UiListAttributeNode}.
*/
public class BooleanAttributeDescriptor extends ListAttributeDescriptor {
public BooleanAttributeDescriptor(String xmlLocalName, String uiName, String nsUri,
String tooltip) {
super(xmlLocalName, uiName, nsUri, tooltip,
new String[] { "true", "false" } );
}
}

View File

@@ -0,0 +1,809 @@
/*
* 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.editors.descriptors;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.resources.ResourceType;
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo;
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo.Format;
import com.android.ide.eclipse.editors.layout.LayoutConstants;
import com.android.ide.eclipse.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
import org.eclipse.swt.graphics.Image;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Utility methods related to descriptors handling.
*/
public final class DescriptorsUtils {
private static final String DEFAULT_WIDGET_PREFIX = "widget";
private static final int JAVADOC_BREAK_LENGTH = 60;
/**
* The path in the online documentation for the manifest description.
* <p/>
* This is NOT a complete URL. To be used, it needs to be appended
* to {@link AndroidConstants#CODESITE_BASE_URL} or to the local SDK
* documentation.
*/
public static final String MANIFEST_SDK_URL = "/reference/android/R.styleable.html#"; //$NON-NLS-1$
public static final String IMAGE_KEY = "image"; //$NON-NLS-1$
private static final String CODE = "$code"; //$NON-NLS-1$
private static final String LINK = "$link"; //$NON-NLS-1$
private static final String ELEM = "$elem"; //$NON-NLS-1$
private static final String BREAK = "$break"; //$NON-NLS-1$
/**
* The {@link ITextAttributeCreator} interface is used by the appendAttribute() method
* to provide a way for caller to override the kind of {@link TextAttributeDescriptor}
* created for a give XML attribute name.
*/
public interface ITextAttributeCreator {
/**
* Creates a new {@link TextAttributeDescriptor} instance for the given XML name,
* UI name and tooltip.
*
* @param xmlName The XML attribute name.
* @param uiName The UI attribute name.
* @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
* See {@link AndroidConstants#NS_RESOURCES} for a common value.
* @param tooltip An optional tooltip.
* @return A new {@link TextAttributeDescriptor} (or derived) instance.
*/
public TextAttributeDescriptor create(String xmlName, String uiName, String nsUri,
String tooltip);
}
/**
* Add all {@link AttributeInfo} to the the array of {@link AttributeDescriptor}.
*
* @param attributes The list of {@link AttributeDescriptor} to append to
* @param elementXmlName Optional XML local name of the element to which attributes are
* being added. When not null, this is used to filter overrides.
* @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
* See {@link AndroidConstants#NS_RESOURCES} for a common value.
* @param infos The array of {@link AttributeInfo} to read and append to attributes
* @param requiredAttributes An optional list of attributes to mark as "required" (i.e. append
* a "*" to their UI name as a hint for the user.)
* @param overrides A map [attribute name => TextAttributeDescriptor creator]. A creator
* can either by a Class<? extends TextAttributeDescriptor> or an instance of
* {@link ITextAttributeCreator} that instantiates the right TextAttributeDescriptor.
*/
public static void appendAttributes(ArrayList<AttributeDescriptor> attributes,
String elementXmlName,
String nsUri, AttributeInfo[] infos,
String[] requiredAttributes,
Map<String, Object> overrides) {
for (AttributeInfo info : infos) {
boolean required = false;
if (requiredAttributes != null) {
for(String attr_name : requiredAttributes) {
if (attr_name.equals(info.getName())) {
required = true;
break;
}
}
}
appendAttribute(attributes, elementXmlName, nsUri, info, required, overrides);
}
}
/**
* Add an {@link AttributeInfo} to the the array of {@link AttributeDescriptor}.
*
* @param attributes The list of {@link AttributeDescriptor} to append to
* @param elementXmlName Optional XML local name of the element to which attributes are
* being added. When not null, this is used to filter overrides.
* @param info The {@link AttributeInfo} to append to attributes
* @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
* See {@link AndroidConstants#NS_RESOURCES} for a common value.
* @param required True if the attribute is to be marked as "required" (i.e. append
* a "*" to its UI name as a hint for the user.)
* @param overrides A map [attribute name => TextAttributeDescriptor creator]. A creator
* can either by a Class<? extends TextAttributeDescriptor> or an instance of
* {@link ITextAttributeCreator} that instantiates the right TextAttributeDescriptor.
*/
public static void appendAttribute(ArrayList<AttributeDescriptor> attributes,
String elementXmlName,
String nsUri,
AttributeInfo info, boolean required,
Map<String, Object> overrides) {
AttributeDescriptor attr = null;
String xmlLocalName = info.getName();
String uiName = prettyAttributeUiName(info.getName()); // ui_name
if (required) {
uiName += "*"; //$NON-NLS-1$
}
String tooltip = formatTooltip(info.getJavaDoc()); // tooltip
// Add the known types to the tooltip
Format[] formats_list = info.getFormats();
int flen = formats_list.length;
if (flen > 0) {
// Fill the formats in a set for faster access
HashSet<Format> formats_set = new HashSet<Format>();
StringBuilder sb = new StringBuilder();
if (tooltip != null) {
sb.append(tooltip);
sb.append(" "); //$NON-NLS-1$
}
sb.append("["); //$NON-NLS-1$
for (int i = 0; i < flen; i++) {
Format f = formats_list[i];
formats_set.add(f);
sb.append(f.toString().toLowerCase());
if (i < flen - 1) {
sb.append(", "); //$NON-NLS-1$
}
}
// The extra space at the end makes the tooltip more readable on Windows.
sb.append("]"); //$NON-NLS-1$
if (required) {
sb.append(". Required.");
}
// The extra space at the end makes the tooltip more readable on Windows.
sb.append(" "); //$NON-NLS-1$
tooltip = sb.toString();
// Create a specialized attribute if we can
if (overrides != null) {
for (Entry<String, Object> entry: overrides.entrySet()) {
String key = entry.getKey();
String elements[] = key.split("/");
String overrideAttrLocalName = null;
if (elements.length < 1) {
continue;
} else if (elements.length == 1) {
overrideAttrLocalName = elements[0];
elements = null;
} else {
overrideAttrLocalName = elements[elements.length - 1];
elements = elements[0].split(",");
}
if (overrideAttrLocalName == null ||
!overrideAttrLocalName.equals(xmlLocalName)) {
continue;
}
boolean ok_element = elements.length < 1;
if (!ok_element) {
for (String element : elements) {
if (element.equals("*") || element.equals(elementXmlName)) {
ok_element = true;
break;
}
}
}
if (!ok_element) {
continue;
}
Object override = entry.getValue();
if (override instanceof Class) {
try {
// The override is instance of the class to create, which must
// have a constructor compatible with TextAttributeDescriptor.
@SuppressWarnings("unchecked") //$NON-NLS-1$
Class<? extends TextAttributeDescriptor> clazz =
(Class<? extends TextAttributeDescriptor>) override;
Constructor<? extends TextAttributeDescriptor> cons;
cons = clazz.getConstructor(new Class<?>[] {
String.class, String.class, String.class, String.class } );
attr = cons.newInstance(
new Object[] { xmlLocalName, uiName, nsUri, tooltip });
} catch (SecurityException e) {
// ignore
} catch (NoSuchMethodException e) {
// ignore
} catch (IllegalArgumentException e) {
// ignore
} catch (InstantiationException e) {
// ignore
} catch (IllegalAccessException e) {
// ignore
} catch (InvocationTargetException e) {
// ignore
}
} else if (override instanceof ITextAttributeCreator) {
attr = ((ITextAttributeCreator) override).create(
xmlLocalName, uiName, nsUri, tooltip);
}
}
} // if overrides
// Create a specialized descriptor if we can, based on type
if (attr == null) {
if (formats_set.contains(Format.REFERENCE)) {
// This is either a multi-type reference or a generic reference.
attr = new ReferenceAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip);
} else if (formats_set.contains(Format.ENUM)) {
attr = new ListAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip,
info.getEnumValues());
} else if (formats_set.contains(Format.FLAG)) {
attr = new FlagAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip,
info.getFlagValues());
} else if (formats_set.contains(Format.BOOLEAN)) {
attr = new BooleanAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip);
} else if (formats_set.contains(Format.STRING)) {
attr = new ReferenceAttributeDescriptor(ResourceType.STRING,
xmlLocalName, uiName, nsUri,
tooltip);
}
}
}
// By default a simple text field is used
if (attr == null) {
attr = new TextAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip);
}
attributes.add(attr);
}
/**
* Indicates the the given {@link AttributeInfo} already exists in the ArrayList of
* {@link AttributeDescriptor}. This test for the presence of a descriptor with the same
* XML name.
*
* @param attributes The list of {@link AttributeDescriptor} to compare to.
* @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
* See {@link AndroidConstants#NS_RESOURCES} for a common value.
* @param info The {@link AttributeInfo} to know whether it is included in the above list.
* @return True if this {@link AttributeInfo} is already present in
* the {@link AttributeDescriptor} list.
*/
public static boolean containsAttribute(ArrayList<AttributeDescriptor> attributes,
String nsUri,
AttributeInfo info) {
String xmlLocalName = info.getName();
for (AttributeDescriptor desc : attributes) {
if (desc.getXmlLocalName().equals(xmlLocalName)) {
if (nsUri == desc.getNamespaceUri() ||
(nsUri != null && nsUri.equals(desc.getNamespaceUri()))) {
return true;
}
}
}
return false;
}
/**
* Create a pretty attribute UI name from an XML name.
* <p/>
* The original xml name starts with a lower case and is camel-case,
* e.g. "maxWidthForView". The pretty name starts with an upper case
* and has space separators, e.g. "Max width for view".
*/
public static String prettyAttributeUiName(String name) {
if (name.length() < 1) {
return name;
}
StringBuffer buf = new StringBuffer();
char c = name.charAt(0);
// Use upper case initial letter
buf.append((char)(c >= 'a' && c <= 'z' ? c + 'A' - 'a' : c));
int len = name.length();
for (int i = 1; i < len; i++) {
c = name.charAt(i);
if (c >= 'A' && c <= 'Z') {
// Break camel case into separate words
buf.append(' ');
// Use a lower case initial letter for the next word, except if the
// word is solely X, Y or Z.
if (c >= 'X' && c <= 'Z' &&
(i == len-1 ||
(i < len-1 && name.charAt(i+1) >= 'A' && name.charAt(i+1) <= 'Z'))) {
buf.append(c);
} else {
buf.append((char)(c - 'A' + 'a'));
}
} else if (c == '_') {
buf.append(' ');
} else {
buf.append(c);
}
}
name = buf.toString();
// Replace these acronyms by upper-case versions
// - (?<=^| ) means "if preceded by a space or beginning of string"
// - (?=$| ) means "if followed by a space or end of string"
name = name.replaceAll("(?<=^| )sdk(?=$| )", "SDK");
name = name.replaceAll("(?<=^| )uri(?=$| )", "URI");
return name;
}
/**
* Capitalizes the string, i.e. transforms the initial [a-z] into [A-Z].
* Returns the string unmodified if the first character is not [a-z].
*
* @param str The string to capitalize.
* @return The capitalized string
*/
public static String capitalize(String str) {
if (str == null || str.length() < 1 || str.charAt(0) < 'a' || str.charAt(0) > 'z') {
return str;
}
StringBuilder sb = new StringBuilder();
sb.append((char)(str.charAt(0) + 'A' - 'a'));
sb.append(str.substring(1));
return sb.toString();
}
/**
* Formats the javadoc tooltip to be usable in a tooltip.
*/
public static String formatTooltip(String javadoc) {
ArrayList<String> spans = scanJavadoc(javadoc);
StringBuilder sb = new StringBuilder();
boolean needBreak = false;
for (int n = spans.size(), i = 0; i < n; ++i) {
String s = spans.get(i);
if (CODE.equals(s)) {
s = spans.get(++i);
if (s != null) {
sb.append('"').append(s).append('"');
}
} else if (LINK.equals(s)) {
String base = spans.get(++i);
String anchor = spans.get(++i);
String text = spans.get(++i);
if (base != null) {
base = base.trim();
}
if (anchor != null) {
anchor = anchor.trim();
}
if (text != null) {
text = text.trim();
}
// If there's no text, use the anchor if there's one
if (text == null || text.length() == 0) {
text = anchor;
}
if (base != null && base.length() > 0) {
if (text == null || text.length() == 0) {
// If we still have no text, use the base as text
text = base;
}
}
if (text != null) {
sb.append(text);
}
} else if (ELEM.equals(s)) {
s = spans.get(++i);
if (s != null) {
sb.append(s);
}
} else if (BREAK.equals(s)) {
needBreak = true;
} else if (s != null) {
if (needBreak && s.trim().length() > 0) {
sb.append('\r');
}
sb.append(s);
needBreak = false;
}
}
return sb.toString();
}
/**
* Formats the javadoc tooltip to be usable in a FormText.
* <p/>
* If the descriptor can provide an icon, the caller should provide
* elementsDescriptor.getIcon() as "image" to FormText, e.g.:
* <code>formText.setImage(IMAGE_KEY, elementsDescriptor.getIcon());</code>
*
* @param javadoc The javadoc to format. Cannot be null.
* @param elementDescriptor The element descriptor parent of the javadoc. Cannot be null.
* @param androidDocBaseUrl The base URL for the documentation. Cannot be null. Should be
* <code>FrameworkResourceManager.getInstance().getDocumentationBaseUrl()</code>
*/
public static String formatFormText(String javadoc,
ElementDescriptor elementDescriptor,
String androidDocBaseUrl) {
ArrayList<String> spans = scanJavadoc(javadoc);
String fullSdkUrl = androidDocBaseUrl + MANIFEST_SDK_URL;
String sdkUrl = elementDescriptor.getSdkUrl();
if (sdkUrl != null && sdkUrl.startsWith(MANIFEST_SDK_URL)) {
fullSdkUrl = androidDocBaseUrl + sdkUrl;
}
StringBuilder sb = new StringBuilder();
Image icon = elementDescriptor.getIcon();
if (icon != null) {
sb.append("<form><li style=\"image\" value=\"" + //$NON-NLS-1$
IMAGE_KEY + "\">"); //$NON-NLS-1$
} else {
sb.append("<form><p>"); //$NON-NLS-1$
}
for (int n = spans.size(), i = 0; i < n; ++i) {
String s = spans.get(i);
if (CODE.equals(s)) {
s = spans.get(++i);
if (elementDescriptor.getXmlName().equals(s) && fullSdkUrl != null) {
sb.append("<a href=\""); //$NON-NLS-1$
sb.append(fullSdkUrl);
sb.append("\">"); //$NON-NLS-1$
sb.append(s);
sb.append("</a>"); //$NON-NLS-1$
} else if (s != null) {
sb.append('"').append(s).append('"');
}
} else if (LINK.equals(s)) {
String base = spans.get(++i);
String anchor = spans.get(++i);
String text = spans.get(++i);
if (base != null) {
base = base.trim();
}
if (anchor != null) {
anchor = anchor.trim();
}
if (text != null) {
text = text.trim();
}
// If there's no text, use the anchor if there's one
if (text == null || text.length() == 0) {
text = anchor;
}
// TODO specialize with a base URL for views, menus & other resources
// Base is empty for a local page anchor, in which case we'll replace it
// by the element SDK URL if it exists.
if ((base == null || base.length() == 0) && fullSdkUrl != null) {
base = fullSdkUrl;
}
String url = null;
if (base != null && base.length() > 0) {
if (base.startsWith("http")) { //$NON-NLS-1$
// If base looks an URL, use it, with the optional anchor
url = base;
if (anchor != null && anchor.length() > 0) {
// If the base URL already has an anchor, it needs to be
// removed first. If there's no anchor, we need to add "#"
int pos = url.lastIndexOf('#');
if (pos < 0) {
url += "#"; //$NON-NLS-1$
} else if (pos < url.length() - 1) {
url = url.substring(0, pos + 1);
}
url += anchor;
}
} else if (text == null || text.length() == 0) {
// If we still have no text, use the base as text
text = base;
}
}
if (url != null && text != null) {
sb.append("<a href=\""); //$NON-NLS-1$
sb.append(url);
sb.append("\">"); //$NON-NLS-1$
sb.append(text);
sb.append("</a>"); //$NON-NLS-1$
} else if (text != null) {
sb.append("<b>").append(text).append("</b>"); //$NON-NLS-1$ //$NON-NLS-2$
}
} else if (ELEM.equals(s)) {
s = spans.get(++i);
if (sdkUrl != null && s != null) {
sb.append("<a href=\""); //$NON-NLS-1$
sb.append(sdkUrl);
sb.append("\">"); //$NON-NLS-1$
sb.append(s);
sb.append("</a>"); //$NON-NLS-1$
} else if (s != null) {
sb.append("<b>").append(s).append("</b>"); //$NON-NLS-1$ //$NON-NLS-2$
}
} else if (BREAK.equals(s)) {
// ignore line breaks in pseudo-HTML rendering
} else if (s != null) {
sb.append(s);
}
}
if (icon != null) {
sb.append("</li></form>"); //$NON-NLS-1$
} else {
sb.append("</p></form>"); //$NON-NLS-1$
}
return sb.toString();
}
private static ArrayList<String> scanJavadoc(String javadoc) {
ArrayList<String> spans = new ArrayList<String>();
// Standardize all whitespace in the javadoc to single spaces.
if (javadoc != null) {
javadoc = javadoc.replaceAll("[ \t\f\r\n]+", " "); //$NON-NLS-1$ //$NON-NLS-2$
}
// Detects {@link <base>#<name> <text>} where all 3 are optional
Pattern p_link = Pattern.compile("\\{@link\\s+([^#\\}\\s]*)(?:#([^\\s\\}]*))?(?:\\s*([^\\}]*))?\\}(.*)"); //$NON-NLS-1$
// Detects <code>blah</code>
Pattern p_code = Pattern.compile("<code>(.+?)</code>(.*)"); //$NON-NLS-1$
// Detects @blah@, used in hard-coded tooltip descriptors
Pattern p_elem = Pattern.compile("@([\\w -]+)@(.*)"); //$NON-NLS-1$
// Detects a buffer that starts by @ < or { (one that was not matched above)
Pattern p_open = Pattern.compile("([@<\\{])(.*)"); //$NON-NLS-1$
// Detects everything till the next potential separator, i.e. @ < or {
Pattern p_text = Pattern.compile("([^@<\\{]+)(.*)"); //$NON-NLS-1$
int currentLength = 0;
String text = null;
while(javadoc != null && javadoc.length() > 0) {
Matcher m;
String s = null;
if ((m = p_code.matcher(javadoc)).matches()) {
spans.add(CODE);
spans.add(text = cleanupJavadocHtml(m.group(1))); // <code> text
javadoc = m.group(2);
if (text != null) {
currentLength += text.length();
}
} else if ((m = p_link.matcher(javadoc)).matches()) {
spans.add(LINK);
spans.add(m.group(1)); // @link base
spans.add(m.group(2)); // @link anchor
spans.add(text = cleanupJavadocHtml(m.group(3))); // @link text
javadoc = m.group(4);
if (text != null) {
currentLength += text.length();
}
} else if ((m = p_elem.matcher(javadoc)).matches()) {
spans.add(ELEM);
spans.add(text = cleanupJavadocHtml(m.group(1))); // @text@
javadoc = m.group(2);
if (text != null) {
currentLength += text.length() - 2;
}
} else if ((m = p_open.matcher(javadoc)).matches()) {
s = m.group(1);
javadoc = m.group(2);
} else if ((m = p_text.matcher(javadoc)).matches()) {
s = m.group(1);
javadoc = m.group(2);
} else {
// This is not supposed to happen. In case of, just use everything.
s = javadoc;
javadoc = null;
}
if (s != null && s.length() > 0) {
s = cleanupJavadocHtml(s);
if (currentLength >= JAVADOC_BREAK_LENGTH) {
spans.add(BREAK);
currentLength = 0;
}
while (currentLength + s.length() > JAVADOC_BREAK_LENGTH) {
int pos = s.indexOf(' ', JAVADOC_BREAK_LENGTH - currentLength);
if (pos <= 0) {
break;
}
spans.add(s.substring(0, pos + 1));
spans.add(BREAK);
currentLength = 0;
s = s.substring(pos + 1);
}
spans.add(s);
currentLength += s.length();
}
}
return spans;
}
/**
* Remove anything that looks like HTML from a javadoc snippet, as it is supported
* neither by FormText nor a standard text tooltip.
*/
private static String cleanupJavadocHtml(String s) {
if (s != null) {
s = s.replaceAll("&lt;", "\""); //$NON-NLS-1$ $NON-NLS-2$
s = s.replaceAll("&gt;", "\""); //$NON-NLS-1$ $NON-NLS-2$
s = s.replaceAll("<[^>]+>", ""); //$NON-NLS-1$ $NON-NLS-2$
}
return s;
}
/**
* Sets the default layout attributes for the a new UiElementNode.
* <p/>
* Note that ideally the node should already be part of a hierarchy so that its
* parent layout and previous sibling can be determined, if any.
* <p/>
* This does not override attributes which are not empty.
*/
public static void setDefaultLayoutAttributes(UiElementNode ui_node, boolean updateLayout) {
// if this ui_node is a layout and we're adding it to a document, use fill_parent for
// both W/H. Otherwise default to wrap_layout.
boolean fill = ui_node.getDescriptor().hasChildren() &&
ui_node.getUiParent() instanceof UiDocumentNode;
ui_node.setAttributeValue(LayoutConstants.ATTR_LAYOUT_WIDTH,
fill ? LayoutConstants.VALUE_FILL_PARENT : LayoutConstants.VALUE_WRAP_CONTENT,
false /* override */);
ui_node.setAttributeValue(LayoutConstants.ATTR_LAYOUT_HEIGHT,
fill ? LayoutConstants.VALUE_FILL_PARENT : LayoutConstants.VALUE_WRAP_CONTENT,
false /* override */);
String widget_id = getFreeWidgetId(ui_node.getUiRoot(),
new Object[] { ui_node.getDescriptor().getXmlLocalName(), null, null, null });
if (widget_id != null) {
ui_node.setAttributeValue(LayoutConstants.ATTR_ID, "@+id/" + widget_id, //$NON-NLS-1$
false /* override */);
}
ui_node.setAttributeValue(LayoutConstants.ATTR_TEXT, widget_id, false /*override*/);
if (updateLayout) {
UiElementNode ui_parent = ui_node.getUiParent();
if (ui_parent != null &&
ui_parent.getDescriptor().getXmlLocalName().equals(
LayoutConstants.RELATIVE_LAYOUT)) {
UiElementNode ui_previous = ui_node.getUiPreviousSibling();
if (ui_previous != null) {
String id = ui_previous.getAttributeValue(LayoutConstants.ATTR_ID);
if (id != null && id.length() > 0) {
id = id.replace("@+", "@"); //$NON-NLS-1$ //$NON-NLS-2$
ui_node.setAttributeValue(LayoutConstants.ATTR_LAYOUT_BELOW, id,
false /* override */);
}
}
}
}
}
/**
* Given a UI root node, returns the first available id that matches the
* pattern "prefix%02d".
*
* @param uiNode The UI node that gives the prefix to match.
* @return A suitable generated id
*/
public static String getFreeWidgetId(UiElementNode uiNode) {
return getFreeWidgetId(uiNode.getUiRoot(),
new Object[] { uiNode.getDescriptor().getXmlLocalName(), null, null, null });
}
/**
* Given a UI root node, returns the first available id that matches the
* pattern "prefix%02d".
*
* For recursion purposes, a "context" is given. Since Java doesn't have in-out parameters
* in methods and we're not going to do a dedicated type, we just use an object array which
* must contain one initial item and several are built on the fly just for internal storage:
* <ul>
* <li> prefix(String): The prefix of the generated id, i.e. "widget". Cannot be null.
* <li> index(Integer): The minimum index of the generated id. Must start with null.
* <li> generated(String): The generated widget currently being searched. Must start with null.
* <li> map(Set<String>): A set of the ids collected so far when walking through the widget
* hierarchy. Must start with null.
* </ul>
*
* @param uiRoot The Ui root node where to start searching recursively. For the initial call
* you want to pass the document root.
* @param params An in-out context of parameters used during recursion, as explained above.
* @return A suitable generated id
*/
@SuppressWarnings("unchecked")
private static String getFreeWidgetId(UiElementNode uiRoot,
Object[] params) {
Set<String> map = (Set<String>)params[3];
if (map == null) {
params[3] = map = new HashSet<String>();
}
int num = params[1] == null ? 0 : ((Integer)params[1]).intValue();
String generated = (String) params[2];
String prefix = (String) params[0];
if (generated == null) {
int pos = prefix.indexOf('.');
if (pos >= 0) {
prefix = prefix.substring(pos + 1);
}
pos = prefix.indexOf('$');
if (pos >= 0) {
prefix = prefix.substring(pos + 1);
}
prefix = prefix.replaceAll("[^a-zA-Z]", ""); //$NON-NLS-1$ $NON-NLS-2$
if (prefix.length() == 0) {
prefix = DEFAULT_WIDGET_PREFIX;
}
do {
num++;
generated = String.format("%1$s%2$02d", prefix, num); //$NON-NLS-1$
} while (map.contains(generated));
params[0] = prefix;
params[1] = num;
params[2] = generated;
}
String id = uiRoot.getAttributeValue(LayoutConstants.ATTR_ID);
if (id != null) {
id = id.replace("@+id/", ""); //$NON-NLS-1$ $NON-NLS-2$
id = id.replace("@id/", ""); //$NON-NLS-1$ $NON-NLS-2$
if (map.add(id) && map.contains(generated)) {
do {
num++;
generated = String.format("%1$s%2$02d", prefix, num); //$NON-NLS-1$
} while (map.contains(generated));
params[1] = num;
params[2] = generated;
}
}
for (UiElementNode uiChild : uiRoot.getUiChildren()) {
getFreeWidgetId(uiChild, params);
}
// Note: return params[2] (not "generated") since it could have changed during recursion.
return (String) params[2];
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.editors.descriptors;
import com.android.ide.eclipse.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
/**
* {@link DocumentDescriptor} describes the properties expected for an XML document node.
*
* Compared to ElementDescriptor, {@link DocumentDescriptor} does not have XML name nor UI name,
* tooltip, SDK url and attributes list.
* <p/>
* It has a children list which represent all the possible roots of the document.
* <p/>
* The document nodes are "mandatory", meaning the UI node is never deleted and it may lack
* an actual XML node attached.
*/
public class DocumentDescriptor extends ElementDescriptor {
/**
* Constructs a new {@link DocumentDescriptor} based on its XML name and children list.
* The UI name is build by capitalizing the XML name.
* The UI nodes will be non-mandatory.
* <p/>
* The XML name is never shown in the UI directly. It is however used when an icon
* needs to be found for the node.
*
* @param xml_name The XML element node name. Case sensitive.
* @param children The list of allowed children. Can be null or empty.
*/
public DocumentDescriptor(String xml_name, ElementDescriptor[] children) {
super(xml_name, children, true /* mandatory */);
}
/**
* @return A new {@link UiElementNode} linked to this descriptor.
*/
@Override
public UiElementNode createUiNode() {
return new UiDocumentNode(this);
}
}

View File

@@ -0,0 +1,318 @@
/*
* 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.editors.descriptors;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.editors.IconFactory;
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Image;
import java.util.HashSet;
import java.util.Set;
/**
* {@link ElementDescriptor} describes the properties expected for a given XML element node.
*
* {@link ElementDescriptor} have an XML name, UI name, a tooltip, an SDK url,
* an attributes list and a children list.
*
* An UI node can be "mandatory", meaning the UI node is never deleted and it may lack
* an actual XML node attached. A non-mandatory UI node MUST have an XML node attached
* and it will cease to exist when the XML node ceases to exist.
*/
public class ElementDescriptor {
/** The XML element node name. Case sensitive. */
private String mXmlName;
/** The XML element name for the user interface, typically capitalized. */
private String mUiName;
/** The list of allowed attributes. */
private AttributeDescriptor[] mAttributes;
/** The list of allowed children */
private ElementDescriptor[] mChildren;
/* An optional tooltip. Can be empty. */
private String mTooltip;
/** An optional SKD URL. Can be empty. */
private String mSdkUrl;
/** Whether this UI node must always exist (even for empty models). */
private boolean mMandatory;
/**
* Constructs a new {@link ElementDescriptor} based on its XML name, UI name,
* tooltip, SDK url, attributes list, children list and mandatory.
*
* @param xml_name The XML element node name. Case sensitive.
* @param ui_name The XML element name for the user interface, typically capitalized.
* @param tooltip An optional tooltip. Can be null or empty.
* @param sdk_url An optional SKD URL. Can be null or empty.
* @param attributes The list of allowed attributes. Can be null or empty.
* @param children The list of allowed children. Can be null or empty.
* @param mandatory Whether this node must always exist (even for empty models). A mandatory
* UI node is never deleted and it may lack an actual XML node attached. A non-mandatory
* UI node MUST have an XML node attached and it will cease to exist when the XML node
* ceases to exist.
*/
public ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url,
AttributeDescriptor[] attributes,
ElementDescriptor[] children,
boolean mandatory) {
mMandatory = mandatory;
mXmlName = xml_name;
mUiName = ui_name;
mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null;
mSdkUrl = (sdk_url != null && sdk_url.length() > 0) ? sdk_url : null;
setAttributes(attributes != null ? attributes : new AttributeDescriptor[]{});
mChildren = children != null ? children : new ElementDescriptor[]{};
}
/**
* Constructs a new {@link ElementDescriptor} based on its XML name and children list.
* The UI name is build by capitalizing the XML name.
* The UI nodes will be non-mandatory.
*
* @param xml_name The XML element node name. Case sensitive.
* @param children The list of allowed children. Can be null or empty.
* @param mandatory Whether this node must always exist (even for empty models). A mandatory
* UI node is never deleted and it may lack an actual XML node attached. A non-mandatory
* UI node MUST have an XML node attached and it will cease to exist when the XML node
* ceases to exist.
*/
public ElementDescriptor(String xml_name, ElementDescriptor[] children, boolean mandatory) {
this(xml_name, prettyName(xml_name), null, null, null, children, mandatory);
}
/**
* Constructs a new {@link ElementDescriptor} based on its XML name and children list.
* The UI name is build by capitalizing the XML name.
* The UI nodes will be non-mandatory.
*
* @param xml_name The XML element node name. Case sensitive.
* @param children The list of allowed children. Can be null or empty.
*/
public ElementDescriptor(String xml_name, ElementDescriptor[] children) {
this(xml_name, prettyName(xml_name), null, null, null, children, false);
}
/**
* Constructs a new {@link ElementDescriptor} based on its XML name.
* The UI name is build by capitalizing the XML name.
* The UI nodes will be non-mandatory.
*
* @param xml_name The XML element node name. Case sensitive.
*/
public ElementDescriptor(String xml_name) {
this(xml_name, prettyName(xml_name), null, null, null, null, false);
}
/** Returns whether this node must always exist (even for empty models) */
public boolean isMandatory() {
return mMandatory;
}
/**
* Returns the XML element node local name (case sensitive)
*/
public final String getXmlLocalName() {
int pos = mXmlName.indexOf(':');
if (pos != -1) {
return mXmlName.substring(pos+1);
}
return mXmlName;
}
/** Returns the XML element node name. Case sensitive. */
public String getXmlName() {
return mXmlName;
}
/**
* Returns the namespace of the attribute.
*/
public final String getNamespace() {
// For now we hard-code the prefix as being "android"
if (mXmlName.startsWith("android:")) { //$NON-NLs-1$
return AndroidConstants.NS_RESOURCES;
}
return ""; //$NON-NLs-1$
}
/** Returns the XML element name for the user interface, typically capitalized. */
public String getUiName() {
return mUiName;
}
/**
* Returns an optional icon for the element.
* <p/>
* By default this tries to return an icon based on the XML name of the element.
* If this fails, it tries to return the default Android logo as defined in the
* plugin. If all fails, it returns null.
*
* @return An icon for this element or null.
*/
public Image getIcon() {
IconFactory factory = IconFactory.getInstance();
int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN;
int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE;
Image icon = factory.getIcon(mXmlName, color, shape);
return icon != null ? icon : AdtPlugin.getAndroidLogo();
}
/**
* Returns an optional ImageDescriptor for the element.
* <p/>
* By default this tries to return an image based on the XML name of the element.
* If this fails, it tries to return the default Android logo as defined in the
* plugin. If all fails, it returns null.
*
* @return An ImageDescriptor for this element or null.
*/
public ImageDescriptor getImageDescriptor() {
IconFactory factory = IconFactory.getInstance();
int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN;
int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE;
ImageDescriptor id = factory.getImageDescriptor(mXmlName, color, shape);
return id != null ? id : AdtPlugin.getAndroidLogoDesc();
}
/* Returns the list of allowed attributes. */
public AttributeDescriptor[] getAttributes() {
return mAttributes;
}
/* Sets the list of allowed attributes. */
public void setAttributes(AttributeDescriptor[] attributes) {
mAttributes = attributes;
for (AttributeDescriptor attribute : attributes) {
attribute.setParent(this);
}
}
/** Returns the list of allowed children */
public ElementDescriptor[] getChildren() {
return mChildren;
}
/** @return True if this descriptor has children available */
public boolean hasChildren() {
return mChildren.length > 0;
}
/** Sets the list of allowed children. */
public void setChildren(ElementDescriptor[] newChildren) {
mChildren = newChildren;
}
/**
* Returns an optional tooltip. Will be null if not present.
* <p/>
* The tooltip is based on the Javadoc of the element and already processed via
* {@link DescriptorsUtils#formatTooltip(String)} to be displayed right away as
* a UI tooltip.
*/
public String getTooltip() {
return mTooltip;
}
/** Returns an optional SKD URL. Will be null if not present. */
public String getSdkUrl() {
return mSdkUrl;
}
/** Sets the optional tooltip. Can be null or empty. */
public void setTooltip(String tooltip) {
mTooltip = tooltip;
}
/** Sets the optional SDK URL. Can be null or empty. */
public void setSdkUrl(String sdkUrl) {
mSdkUrl = sdkUrl;
}
/**
* @return A new {@link UiElementNode} linked to this descriptor.
*/
public UiElementNode createUiNode() {
return new UiElementNode(this);
}
/**
* Returns the first children of this descriptor that describes the given XML element name.
* <p/>
* In recursive mode, searches the direct children first before descending in the hierarchy.
*
* @return The ElementDescriptor matching the requested XML node element name or null.
*/
public ElementDescriptor findChildrenDescriptor(String element_name, boolean recursive) {
return findChildrenDescriptorInternal(element_name, recursive, null);
}
private ElementDescriptor findChildrenDescriptorInternal(String element_name,
boolean recursive,
Set<ElementDescriptor> visited) {
if (recursive && visited == null) {
visited = new HashSet<ElementDescriptor>();
}
for (ElementDescriptor e : getChildren()) {
if (e.getXmlName().equals(element_name)) {
return e;
}
}
if (visited != null) {
visited.add(this);
}
if (recursive) {
for (ElementDescriptor e : getChildren()) {
if (visited != null) {
if (!visited.add(e)) { // Set.add() returns false if element is already present
continue;
}
}
ElementDescriptor f = e.findChildrenDescriptorInternal(element_name,
recursive, visited);
if (f != null) {
return f;
}
}
}
return null;
}
/**
* Utility helper than pretty-formats an XML Name for the UI.
* This is used by the simplified constructor that takes only an XML element name.
*
* @param xml_name The XML name to convert.
* @return The XML name with dashes replaced by spaces and capitalized.
*/
private static String prettyName(String xml_name) {
char c[] = xml_name.toCharArray();
if (c.length > 0) {
c[0] = Character.toUpperCase(c[0]);
}
return new String(c).replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.editors.descriptors;
import com.android.ide.eclipse.editors.uimodel.UiAttributeNode;
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode;
/**
* Describes a text attribute that can only contains some predefined values.
* It is displayed by a {@link UiListAttributeNode}.
*/
public class EnumAttributeDescriptor extends ListAttributeDescriptor {
public EnumAttributeDescriptor(String xmlLocalName, String uiName, String nsUri,
String tooltip) {
super(xmlLocalName, uiName, nsUri, tooltip);
}
/**
* @return A new {@link UiListAttributeNode} linked to this descriptor.
*/
@Override
public UiAttributeNode createUiNode(UiElementNode uiParent) {
return new UiListAttributeNode(this, uiParent);
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.editors.descriptors;
import com.android.ide.eclipse.editors.ui.FlagValueCellEditor;
import com.android.ide.eclipse.editors.uimodel.UiAttributeNode;
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.editors.uimodel.UiFlagAttributeNode;
import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.swt.widgets.Composite;
/**
* Describes a text attribute that can only contains some predefined values.
* It is displayed by a {@link UiListAttributeNode}.
*
* Note: in Android resources, a "flag" is a list of fixed values where one or
* more values can be selected using an "or", e.g. "align='left|top'".
* By contrast, an "enum" is a list of fixed values of which only one can be
* selected at a given time, e.g. "gravity='right'".
* <p/>
* This class handles the "flag" case.
* The "enum" case is done using {@link ListAttributeDescriptor}.
*/
public class FlagAttributeDescriptor extends TextAttributeDescriptor {
private String[] mNames;
/**
* Creates a new {@link FlagAttributeDescriptor} which automatically gets its
* values from the FrameworkResourceManager.
*/
public FlagAttributeDescriptor(String xmlLocalName, String uiName, String nsUri,
String tooltip) {
super(xmlLocalName, uiName, nsUri, tooltip);
}
/**
* Creates a new {@link FlagAttributeDescriptor} which uses the provided values.
*/
public FlagAttributeDescriptor(String xmlLocalName, String uiName, String nsUri,
String tooltip, String[] names) {
super(xmlLocalName, uiName, nsUri, tooltip);
mNames = names;
}
/**
* @return The initial names of the flags. Can be null, in which case the Framework
* resource parser should be checked.
*/
public String[] getNames() {
return mNames;
}
/**
* @return A new {@link UiListAttributeNode} linked to this descriptor.
*/
@Override
public UiAttributeNode createUiNode(UiElementNode uiParent) {
return new UiFlagAttributeNode(this, uiParent);
}
// ------- IPropertyDescriptor Methods
@Override
public CellEditor createPropertyEditor(Composite parent) {
return new FlagValueCellEditor(parent);
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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.editors.descriptors;
public interface IDescriptorProvider {
ElementDescriptor[] getRootElementDescriptors();
ElementDescriptor getDescriptor();
}

Some files were not shown because too many files have changed in this diff Show More