/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.build;
import com.android.ide.eclipse.adt.AndroidConstants;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
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 java.io.File;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class AaptParser {
// TODO: rename the pattern to something that makes sense + javadoc comments.
/**
* Single line aapt warning for skipping files.<br>
* " (skipping hidden file '<file path>'"
*/
private final static Pattern sPattern0Line1 = Pattern.compile(
"^\\s+\\(skipping hidden file\\s'(.*)'\\)$"); //$NON-NLS-1$
/**
* First line of dual line aapt error.<br>
* "ERROR at line <line>: <error>"<br>
* " (Occurred while parsing <path>)"
*/
private final static Pattern sPattern1Line1 = Pattern.compile(
"^ERROR\\s+at\\s+line\\s+(\\d+):\\s+(.*)$"); //$NON-NLS-1$
/**
* Second line of dual line aapt error.<br>
* "ERROR at line <line>: <error>"<br>
* " (Occurred while parsing <path>)"<br>
* @see #sPattern1Line1
*/
private final static Pattern sPattern1Line2 = Pattern.compile(
"^\\s+\\(Occurred while parsing\\s+(.*)\\)$"); //$NON-NLS-1$
/**
* First line of dual line aapt error.<br>
* "ERROR: <error>"<br>
* "Defined at file <path> line <line>"
*/
private final static Pattern sPattern2Line1 = Pattern.compile(
"^ERROR:\\s+(.+)$"); //$NON-NLS-1$
/**
* Second line of dual line aapt error.<br>
* "ERROR: <error>"<br>
* "Defined at file <path> line <line>"<br>
* @see #sPattern2Line1
*/
private final static Pattern sPattern2Line2 = Pattern.compile(
"Defined\\s+at\\s+file\\s+(.+)\\s+line\\s+(\\d+)"); //$NON-NLS-1$
/**
* Single line aapt error<br>
* "<path> line <line>: <error>"
*/
private final static Pattern sPattern3Line1 = Pattern.compile(
"^(.+)\\sline\\s(\\d+):\\s(.+)$"); //$NON-NLS-1$
/**
* First line of dual line aapt error.<br>
* "ERROR parsing XML file <path>"<br>
* "<error> at line <line>"
*/
private final static Pattern sPattern4Line1 = Pattern.compile(
"^Error\\s+parsing\\s+XML\\s+file\\s(.+)$"); //$NON-NLS-1$
/**
* Second line of dual line aapt error.<br>
* "ERROR parsing XML file <path>"<br>
* "<error> at line <line>"<br>
* @see #sPattern4Line1
*/
private final static Pattern sPattern4Line2 = Pattern.compile(
"^(.+)\\s+at\\s+line\\s+(\\d+)$"); //$NON-NLS-1$
/**
* Single line aapt warning<br>
* "<path>:<line>: <error>"
*/
private final static Pattern sPattern5Line1 = Pattern.compile(
"^(.+?):(\\d+):\\s+WARNING:(.+)$"); //$NON-NLS-1$
/**
* Single line aapt error<br>
* "<path>:<line>: <error>"
*/
private final static Pattern sPattern6Line1 = Pattern.compile(
"^(.+?):(\\d+):\\s+(.+)$"); //$NON-NLS-1$
/**
* 4 line aapt error<br>
* "ERROR: 9-path image <path> malformed"<br>
* Line 2 and 3 are taken as-is while line 4 is ignored (it repeats with<br>
* 'ERROR: failure processing <path>)
*/
private final static Pattern sPattern7Line1 = Pattern.compile(
"^ERROR:\\s+9-patch\\s+image\\s+(.+)\\s+malformed\\.$"); //$NON-NLS-1$
private final static Pattern sPattern8Line1 = Pattern.compile(
"^(invalid resource directory name): (.*)$"); //$NON-NLS-1$
/**
* 2 line aapt error<br>
* "ERROR: Invalid configuration: foo"<br>
* " ^^^"<br>
* There's no need to parse the 2nd line.
*/
private final static Pattern sPattern9Line1 = Pattern.compile(
"^Invalid configuration: (.+)$"); //$NON-NLS-1$
/**
* Parse the output of aapt and mark the incorrect file with error markers
*
* @param results the output of aapt
* @param project the project containing the file to mark
* @return true if the parsing failed, false if success.
*/
public static boolean parseOutput(List<String> results, IProject project) {
return parseOutput(results.toArray(new String[results.size()]), project);
}
/**
* Parse the output of aapt and mark the incorrect file with error markers
*
* @param results the output of aapt
* @param project the project containing the file to mark
* @return true if the parsing failed, false if success.
*/
public static boolean parseOutput(String[] results, IProject project) {
// nothing to parse? just return false;
if (results.length == 0) {
return false;
}
// get the root of the project so that we can make IFile from full
// file path
String osRoot = project.getLocation().toOSString();
Matcher m;
for (int i = 0; i < results.length ; i++) {
String p = results[i];
m = sPattern0Line1.matcher(p);
if (m.matches()) {
// we ignore those (as this is an ignore message from aapt)
continue;
}
m = sPattern1Line1.matcher(p);
if (m.matches()) {
String lineStr = m.group(1);
String msg = m.group(2);
// get the matcher for the next line.
m = getNextLineMatcher(results, ++i, sPattern1Line2);
if (m == null) {
return true;
}
String location = m.group(1);
// check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
continue;
}
// this needs to be tested before Pattern2 since they both start with 'ERROR:'
m = sPattern7Line1.matcher(p);
if (m.matches()) {
String location = m.group(1);
String msg = p; // default msg is the line in case we don't find anything else
if (++i < results.length) {
msg = results[i].trim();
if (++i < results.length) {
msg = msg + " - " + results[i].trim(); //$NON-NLS-1$
// skip the next line
i++;
}
}
// display the error
if (checkAndMark(location, null, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
// success, go to the next line
continue;
}
m = sPattern2Line1.matcher(p);
if (m.matches()) {
// get the msg
String msg = m.group(1);
// get the matcher for the next line.
m = getNextLineMatcher(results, ++i, sPattern2Line2);
if (m == null) {
return true;
}
String location = m.group(1);
String lineStr = m.group(2);
// check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
continue;
}
m = sPattern3Line1.matcher(p);
if (m.matches()) {
String location = m.group(1);
String lineStr = m.group(2);
String msg = m.group(3);
// check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
// success, go to the next line
continue;
}
m = sPattern4Line1.matcher(p);
if (m.matches()) {
// get the filename.
String location = m.group(1);
// get the matcher for the next line.
m = getNextLineMatcher(results, ++i, sPattern4Line2);
if (m == null) {
return true;
}
String msg = m.group(1);
String lineStr = m.group(2);
// check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
// success, go to the next line
continue;
}
m = sPattern5Line1.matcher(p);
if (m.matches()) {
String location = m.group(1);
String lineStr = m.group(2);
String msg = m.group(3);
// check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) {
return true;
}
// success, go to the next line
continue;
}
m = sPattern6Line1.matcher(p);
if (m.matches()) {
String location = m.group(1);
String lineStr = m.group(2);
String msg = m.group(3);
// check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
// success, go to the next line
continue;
}
m = sPattern8Line1.matcher(p);
if (m.matches()) {
String location = m.group(2);
String msg = m.group(1);
// check the values and attempt to mark the file.
if (checkAndMark(location, null, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
// success, go to the next line
continue;
}
m = sPattern9Line1.matcher(p);
if (m.matches()) {
String badConfig = m.group(1);
String msg = String.format("APK Configuration filter '%1$s' is invalid", badConfig);
// skip the next line
i++;
// check the values and attempt to mark the file.
if (checkAndMark(null /*location*/, null, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_PACKAGE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
// success, go to the next line
continue;
}
// invalid line format, flag as error, and bail
return true;
}
return false;
}
/**
* Check if the parameters gotten from the error output are valid, and mark
* the file with an AAPT marker.
* @param location the full OS path of the error file. If null, the project is marked
* @param lineStr
* @param message
* @param root The root directory of the project, in OS specific format.
* @param project
* @param markerId The marker id to put.
* @param severity The severity of the marker to put (IMarker.SEVERITY_*)
* @return true if the parameters were valid and the file was marked successfully.
*
* @see IMarker
*/
private static final boolean checkAndMark(String location, String lineStr,
String message, String root, IProject project, String markerId, int severity) {
// check this is in fact a file
if (location != null) {
File f = new File(location);
if (f.exists() == false) {
return false;
}
}
// get the line number
int line = -1; // default value for error with no line.
if (lineStr != null) {
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 false;
}
}
// add the marker
IResource f2 = project;
if (location != null) {
f2 = getResourceFromFullPath(location, root, project);
if (f2 == null) {
return false;
}
}
// check if there's a similar marker already, since aapt is launched twice
boolean markerAlreadyExists = false;
try {
IMarker[] markers = f2.findMarkers(markerId, true, IResource.DEPTH_ZERO);
for (IMarker marker : markers) {
int tmpLine = marker.getAttribute(IMarker.LINE_NUMBER, -1);
if (tmpLine != line) {
break;
}
int tmpSeverity = marker.getAttribute(IMarker.SEVERITY, -1);
if (tmpSeverity != severity) {
break;
}
String tmpMsg = marker.getAttribute(IMarker.MESSAGE, null);
if (tmpMsg == null || tmpMsg.equals(message) == false) {
break;
}
// if we're here, all the marker attributes are equals, we found it
// and exit
markerAlreadyExists = true;
break;
}
} catch (CoreException e) {
// if we couldn't get the markers, then we just mark the file again
// (since markerAlreadyExists is initialized to false, we do nothing)
}
if (markerAlreadyExists == false) {
BaseProjectHelper.markResource(f2, markerId, message, line, severity);
}
return true;
}
/**
* Returns a matching matcher for the next line
* @param lines The array of lines
* @param nextIndex The index of the next line
* @param pattern The pattern to match
* @return null if error or no match, the matcher otherwise.
*/
private static final Matcher getNextLineMatcher(String[] lines,
int nextIndex, Pattern pattern) {
// unless we can't, because we reached the last line
if (nextIndex == lines.length) {
// we expected a 2nd line, so we flag as error
// and we bail
return null;
}
Matcher m = pattern.matcher(lines[nextIndex]);
if (m.matches()) {
return m;
}
return null;
}
private static IResource getResourceFromFullPath(String filename, String root,
IProject project) {
if (filename.startsWith(root)) {
String file = filename.substring(root.length());
// get the resource
IResource r = project.findMember(file);
// if the resource is valid, we add the marker
if (r.exists()) {
return r;
}
}
return null;
}
}