aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java484
1 files changed, 484 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java
new file mode 100644
index 000000000..806fa9c40
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.build;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.io.FileOp;
+
+import org.eclipse.core.resources.IContainer;
+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.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.jdt.core.IJavaProject;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A {@link SourceProcessor} for aidl files.
+ *
+ */
+public class AidlProcessor extends SourceProcessor {
+
+ private static final String PROPERTY_COMPILE_AIDL = "compileAidl"; //$NON-NLS-1$
+
+ /**
+ * Single line aidl error<br>
+ * {@code <path>:<line>: <error>}<br>
+ * or<br>
+ * {@code <path>:<line> <error>}<br>
+ */
+ private static Pattern sAidlPattern1 = Pattern.compile("^(.+?):(\\d+):?\\s(.+)$"); //$NON-NLS-1$
+
+ private final static Set<String> EXTENSIONS = Collections.singleton(SdkConstants.EXT_AIDL);
+
+ private enum AidlType {
+ UNKNOWN, INTERFACE, PARCELABLE;
+ }
+
+ // See comment in #getAidlType()
+// private final static Pattern sParcelablePattern = Pattern.compile(
+// "^\\s*parcelable\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*;\\s*$");
+//
+// private final static Pattern sInterfacePattern = Pattern.compile(
+// "^\\s*interface\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?:\\{.*)?$");
+
+
+ public AidlProcessor(@NonNull IJavaProject javaProject, @NonNull BuildToolInfo buildToolInfo,
+ @NonNull IFolder genFolder) {
+ super(javaProject, buildToolInfo, genFolder);
+ }
+
+ @Override
+ protected Set<String> getExtensions() {
+ return EXTENSIONS;
+ }
+
+ @Override
+ protected String getSavePropertyName() {
+ return PROPERTY_COMPILE_AIDL;
+ }
+
+ @Override
+ protected void doCompileFiles(List<IFile> sources, BaseBuilder builder,
+ IProject project, IAndroidTarget projectTarget,
+ List<IPath> sourceFolders, List<IFile> notCompiledOut, List<File> libraryProjectsOut,
+ IProgressMonitor monitor) throws CoreException {
+ // create the command line
+ List<String> commandList = new ArrayList<String>(
+ 4 + sourceFolders.size() + libraryProjectsOut.size());
+ commandList.add(getBuildToolInfo().getPath(BuildToolInfo.PathId.AIDL));
+ commandList.add(quote("-p" + projectTarget.getPath(IAndroidTarget.ANDROID_AIDL))); //$NON-NLS-1$
+
+ // since the path are relative to the workspace and not the project itself, we need
+ // the workspace root.
+ IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
+ for (IPath p : sourceFolders) {
+ IFolder f = wsRoot.getFolder(p);
+ if (f.exists()) { // if the resource doesn't exist, getLocation will return null.
+ commandList.add(quote("-I" + f.getLocation().toOSString())); //$NON-NLS-1$
+ }
+ }
+
+ for (File libOut : libraryProjectsOut) {
+ // FIXME: make folder configurable
+ File aidlFile = new File(libOut, SdkConstants.FD_AIDL);
+ if (aidlFile.isDirectory()) {
+ commandList.add(quote("-I" + aidlFile.getAbsolutePath())); //$NON-NLS-1$
+ }
+ }
+
+ // convert to array with 2 extra strings for the in/out file paths.
+ int index = commandList.size();
+ String[] commands = commandList.toArray(new String[index + 2]);
+
+ boolean verbose = AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE;
+
+ // remove the generic marker from the project
+ builder.removeMarkersFromResource(project, AdtConstants.MARKER_AIDL);
+
+ // prepare the two output folders.
+ IFolder genFolder = getGenFolder();
+ IFolder projectOut = BaseProjectHelper.getAndroidOutputFolder(project);
+ IFolder aidlOutFolder = projectOut.getFolder(SdkConstants.FD_AIDL);
+ if (aidlOutFolder.exists() == false) {
+ aidlOutFolder.create(true /*force*/, true /*local*/,
+ new SubProgressMonitor(monitor, 10));
+ }
+
+ boolean success = false;
+
+ // loop until we've compile them all
+ for (IFile sourceFile : sources) {
+ if (verbose) {
+ String name = sourceFile.getName();
+ IPath sourceFolderPath = getSourceFolderFor(sourceFile);
+ if (sourceFolderPath != null) {
+ // make a path to the source file relative to the source folder.
+ IPath relative = sourceFile.getFullPath().makeRelativeTo(sourceFolderPath);
+ name = relative.toString();
+ }
+ AdtPlugin.printToConsole(project, "AIDL: " + name);
+ }
+
+ // Remove the AIDL error markers from the aidl file
+ builder.removeMarkersFromResource(sourceFile, AdtConstants.MARKER_AIDL);
+
+ // get the path of the source file.
+ IPath sourcePath = sourceFile.getLocation();
+ String osSourcePath = sourcePath.toOSString();
+
+ // look if we already know the output
+ SourceFileData data = getFileData(sourceFile);
+ if (data == null) {
+ data = new SourceFileData(sourceFile);
+ addData(data);
+ }
+
+ // if there's no output file yet, compute it.
+ if (data.getOutput() == null) {
+ IFile javaFile = getAidlOutputFile(sourceFile, genFolder,
+ true /*replaceExt*/, true /*createFolders*/, monitor);
+ data.setOutputFile(javaFile);
+ }
+
+ // finish to set the command line.
+ commands[index] = quote(osSourcePath);
+ commands[index + 1] = quote(data.getOutput().getLocation().toOSString());
+
+ // launch the process
+ if (execAidl(builder, project, commands, sourceFile, verbose) == false) {
+ // aidl failed. File should be marked. We add the file to the list
+ // of file that will need compilation again.
+ notCompiledOut.add(sourceFile);
+ } else {
+ // Success. we'll return that we generated code
+ setCompilationStatus(COMPILE_STATUS_CODE);
+ success = true;
+
+ // Also copy the file to the bin folder.
+ IFile aidlOutFile = getAidlOutputFile(sourceFile, aidlOutFolder,
+ false /*replaceExt*/, true /*createFolders*/, monitor);
+
+ FileOp op = new FileOp();
+ try {
+ op.copyFile(sourceFile.getLocation().toFile(),
+ aidlOutFile.getLocation().toFile());
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ if (success) {
+ aidlOutFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
+ }
+ }
+
+ @Override
+ protected void loadOutputAndDependencies() {
+ IProgressMonitor monitor = new NullProgressMonitor();
+ IFolder genFolder = getGenFolder();
+
+ Collection<SourceFileData> dataList = getAllFileData();
+ for (SourceFileData data : dataList) {
+ try {
+ IFile javaFile = getAidlOutputFile(data.getSourceFile(), genFolder,
+ true /*replaceExt*/, false /*createFolders*/, monitor);
+ data.setOutputFile(javaFile);
+ } catch (CoreException e) {
+ // ignore, we're not asking to create the folder so this won't happen anyway.
+ }
+
+ }
+ }
+
+ /**
+ * Execute the aidl command line, parse the output, and mark the aidl file
+ * with any reported errors.
+ * @param command the String array containing the command line to execute.
+ * @param file The IFile object representing the aidl file being
+ * compiled.
+ * @param verbose the build verbosity
+ * @return false if the exec failed, and build needs to be aborted.
+ */
+ private boolean execAidl(BaseBuilder builder, IProject project, String[] command, IFile file,
+ boolean verbose) {
+ // do the exec
+ try {
+ if (verbose) {
+ StringBuilder sb = new StringBuilder();
+ for (String c : command) {
+ sb.append(c);
+ sb.append(' ');
+ }
+ String cmd_line = sb.toString();
+ AdtPlugin.printToConsole(project, cmd_line);
+ }
+
+ Process p = Runtime.getRuntime().exec(command);
+
+ // list to store each line of stderr
+ ArrayList<String> stdErr = new ArrayList<String>();
+
+ // get the output and return code from the process
+ int returnCode = BuildHelper.grabProcessOutput(project, p, stdErr);
+
+ if (stdErr.size() > 0) {
+ // attempt to parse the error output
+ boolean parsingError = parseAidlOutput(stdErr, file);
+
+ // If the process failed and we couldn't parse the output
+ // we print a message, mark the project and exit
+ if (returnCode != 0) {
+
+ if (parsingError || verbose) {
+ // display the message in the console.
+ if (parsingError) {
+ AdtPlugin.printErrorToConsole(project, stdErr.toArray());
+
+ // mark the project
+ BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL,
+ Messages.Unparsed_AIDL_Errors, IMarker.SEVERITY_ERROR);
+ } else {
+ AdtPlugin.printToConsole(project, stdErr.toArray());
+ }
+ }
+ return false;
+ }
+ } else if (returnCode != 0) {
+ // no stderr output but exec failed.
+ String msg = String.format(Messages.AIDL_Exec_Error_d, returnCode);
+
+ BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL,
+ msg, IMarker.SEVERITY_ERROR);
+
+ return false;
+ }
+ } catch (IOException e) {
+ // mark the project and exit
+ String msg = String.format(Messages.AIDL_Exec_Error_s, command[0]);
+ BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL, msg,
+ IMarker.SEVERITY_ERROR);
+ return false;
+ } catch (InterruptedException e) {
+ // mark the project and exit
+ String msg = String.format(Messages.AIDL_Exec_Error_s, command[0]);
+ BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL, msg,
+ IMarker.SEVERITY_ERROR);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Parse the output of aidl and mark the file with any errors.
+ * @param lines The output to parse.
+ * @param file The file to mark with error.
+ * @return true if the parsing failed, false if success.
+ */
+ private boolean parseAidlOutput(ArrayList<String> lines, IFile file) {
+ // nothing to parse? just return false;
+ if (lines.size() == 0) {
+ return false;
+ }
+
+ Matcher m;
+
+ for (int i = 0; i < lines.size(); i++) {
+ String p = lines.get(i);
+
+ m = sAidlPattern1.matcher(p);
+ if (m.matches()) {
+ // we can ignore group 1 which is the location since we already
+ // have a IFile object representing the aidl file.
+ String lineStr = m.group(2);
+ String msg = m.group(3);
+
+ // get the line number
+ int line = 0;
+ try {
+ line = Integer.parseInt(lineStr);
+ } catch (NumberFormatException e) {
+ // looks like the string we extracted wasn't a valid
+ // file number. Parsing failed and we return true
+ return true;
+ }
+
+ // mark the file
+ BaseProjectHelper.markResource(file, AdtConstants.MARKER_AIDL, msg, line,
+ IMarker.SEVERITY_ERROR);
+
+ // success, go to the next line
+ continue;
+ }
+
+ // invalid line format, flag as error, and bail
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the {@link IFile} handle to the destination file for a given aidl source file
+ * ({@link AidlData}).
+ * @param sourceFile The source file
+ * @param outputFolder the top level output folder (not including the package folders)
+ * @param createFolders whether or not the parent folder of the destination should be created
+ * if it does not exist.
+ * @param monitor the progress monitor
+ * @return the handle to the destination file.
+ * @throws CoreException
+ */
+ private IFile getAidlOutputFile(IFile sourceFile, IFolder outputFolder, boolean replaceExt,
+ boolean createFolders, IProgressMonitor monitor) throws CoreException {
+
+ IPath sourceFolderPath = getSourceFolderFor(sourceFile);
+
+ // this really shouldn't happen since the sourceFile must be in a source folder
+ // since it comes from the delta visitor
+ if (sourceFolderPath != null) {
+ // make a path to the source file relative to the source folder.
+ IPath relative = sourceFile.getFullPath().makeRelativeTo(sourceFolderPath);
+ // remove the file name. This is now the destination folder.
+ relative = relative.removeLastSegments(1);
+
+ // get an IFolder for this path.
+ IFolder destinationFolder = outputFolder.getFolder(relative);
+
+ // create it if needed.
+ if (destinationFolder.exists() == false && createFolders) {
+ createFolder(destinationFolder, monitor);
+ }
+
+ // Build the Java file name from the aidl name.
+ String javaName;
+ if (replaceExt) {
+ javaName = sourceFile.getName().replaceAll(
+ AdtConstants.RE_AIDL_EXT, SdkConstants.DOT_JAVA);
+ } else {
+ javaName = sourceFile.getName();
+ }
+
+ // get the resource for the java file.
+ IFile javaFile = destinationFolder.getFile(javaName);
+ return javaFile;
+ }
+
+ return null;
+ }
+
+ /**
+ * Creates the destination folder. Because
+ * {@link IFolder#create(boolean, boolean, IProgressMonitor)} only works if the parent folder
+ * already exists, this goes and ensure that all the parent folders actually exist, or it
+ * creates them as well.
+ * @param destinationFolder The folder to create
+ * @param monitor the {@link IProgressMonitor},
+ * @throws CoreException
+ */
+ private void createFolder(IFolder destinationFolder, IProgressMonitor monitor)
+ throws CoreException {
+
+ // check the parent exist and create if necessary.
+ IContainer parent = destinationFolder.getParent();
+ if (parent.getType() == IResource.FOLDER && parent.exists() == false) {
+ createFolder((IFolder)parent, monitor);
+ }
+
+ // create the folder.
+ destinationFolder.create(true /*force*/, true /*local*/,
+ new SubProgressMonitor(monitor, 10));
+ }
+
+ /**
+ * Returns the type of the aidl file. Aidl files can either declare interfaces, or declare
+ * parcelables. This method will attempt to parse the file and return the type. If the type
+ * cannot be determined, then it will return {@link AidlType#UNKNOWN}.
+ * @param file The aidl file
+ * @return the type of the aidl.
+ */
+ private static AidlType getAidlType(IFile file) {
+ // At this time, parsing isn't available, so we return UNKNOWN. This will force
+ // a recompilation of all aidl file as soon as one is changed.
+ return AidlType.UNKNOWN;
+
+ // TODO: properly parse aidl file to determine type and generate dependency graphs.
+//
+// String className = file.getName().substring(0,
+// file.getName().length() - SdkConstants.DOT_AIDL.length());
+//
+// InputStream input = file.getContents(true /* force*/);
+// try {
+// BufferedReader reader = new BufferedReader(new InputStreateader(input));
+// String line;
+// while ((line = reader.readLine()) != null) {
+// if (line.length() == 0) {
+// continue;
+// }
+//
+// Matcher m = sParcelablePattern.matcher(line);
+// if (m.matches() && m.group(1).equals(className)) {
+// return AidlType.PARCELABLE;
+// }
+//
+// m = sInterfacePattern.matcher(line);
+// if (m.matches() && m.group(1).equals(className)) {
+// return AidlType.INTERFACE;
+// }
+// }
+// } catch (IOException e) {
+// throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+// "Error parsing aidl file", e));
+// } finally {
+// try {
+// input.close();
+// } catch (IOException e) {
+// throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+// "Error parsing aidl file", e));
+// }
+// }
+//
+// return AidlType.UNKNOWN;
+ }
+}