| /* |
| * 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.AdtConstants; |
| import com.android.ide.eclipse.adt.AdtPlugin; |
| import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; |
| |
| import org.eclipse.core.resources.IFile; |
| 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.jface.text.FindReplaceDocumentAdapter; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.ui.editors.text.TextFileDocumentProvider; |
| import org.eclipse.ui.texteditor.IDocumentProvider; |
| |
| 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$ |
| |
| /** |
| * Portion of the error message which states the context in which the error occurred, |
| * such as which property was being processed and what the string value was that |
| * caused the error. |
| * <p> |
| * Example: |
| * error: No resource found that matches the given name (at 'text' with value '@string/foo') |
| */ |
| private static final Pattern sValueRangePattern = |
| Pattern.compile("\\(at '(.+)' with value '(.*)'\\)"); //$NON-NLS-1$ |
| |
| |
| /** |
| * Portion of error message which points to the second occurrence of a repeated resource |
| * definition. |
| * <p> |
| * Example: |
| * error: Resource entry repeatedStyle1 already has bag item android:gravity. |
| */ |
| private static final Pattern sRepeatedRangePattern = |
| Pattern.compile("Resource entry (.+) already has bag item (.+)\\."); //$NON-NLS-1$ |
| |
| /** |
| * Error message emitted when aapt skips a file because for example it's name is |
| * invalid, such as a layout file name which starts with _. |
| * <p> |
| * This error message is used by AAPT in Tools 19 and earlier. |
| */ |
| private static final Pattern sSkippingPattern = |
| Pattern.compile(" \\(skipping (.+) .+ '(.*)'\\)"); //$NON-NLS-1$ |
| |
| /** |
| * Error message emitted when aapt skips a file because for example it's name is |
| * invalid, such as a layout file name which starts with _. |
| * <p> |
| * This error message is used by AAPT in Tools 20 and later. |
| */ |
| private static final Pattern sNewSkippingPattern = |
| Pattern.compile(" \\(skipping .+ '(.+)' due to ANDROID_AAPT_IGNORE pattern '.+'\\)"); //$NON-NLS-1$ |
| |
| /** |
| * Suffix of error message which points to the first occurrence of a repeated resource |
| * definition. |
| * Example: |
| * Originally defined here. |
| */ |
| private static final String ORIGINALLY_DEFINED_MSG = "Originally defined here."; //$NON-NLS-1$ |
| |
| /** |
| * Portion of error message which points to the second occurrence of a repeated resource |
| * definition. |
| * <p> |
| * Example: |
| * error: Resource entry repeatedStyle1 already has bag item android:gravity. |
| */ |
| private static final Pattern sNoResourcePattern = |
| Pattern.compile("No resource found that matches the given name: attr '(.+)'\\."); //$NON-NLS-1$ |
| |
| /** |
| * Portion of error message which points to a missing required attribute in a |
| * resource definition. |
| * <p> |
| * Example: |
| * error: error: A 'name' attribute is required for <style> |
| */ |
| private static final Pattern sRequiredPattern = |
| Pattern.compile("A '(.+)' attribute is required for <(.+)>"); //$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$ |
| |
| private final static Pattern sXmlBlockPattern = Pattern.compile( |
| "W/ResourceType\\(.*\\): Bad XML block: no root element node found"); //$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) { |
| int size = results.size(); |
| if (size > 0) { |
| return parseOutput(results.toArray(new String[size]), project); |
| } |
| |
| return false; |
| } |
| |
| /** |
| * 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, |
| AdtConstants.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, |
| AdtConstants.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, |
| AdtConstants.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, |
| AdtConstants.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, |
| AdtConstants.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, |
| AdtConstants.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, |
| AdtConstants.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, |
| AdtConstants.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, |
| AdtConstants.MARKER_AAPT_PACKAGE, IMarker.SEVERITY_ERROR) == false) { |
| return true; |
| } |
| |
| // success, go to the next line |
| continue; |
| } |
| |
| m = sNewSkippingPattern.matcher(p); |
| if (m.matches()) { |
| String location = m.group(1); |
| |
| if (location.startsWith(".") //$NON-NLS-1$ |
| || location.endsWith("~")) { //$NON-NLS-1$ |
| continue; |
| } |
| |
| // check the values and attempt to mark the file. |
| if (checkAndMark(location, null, p.trim(), osRoot, project, |
| AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) { |
| return true; |
| } |
| |
| // success, go to the next line |
| continue; |
| } |
| |
| m = sSkippingPattern.matcher(p); |
| if (m.matches()) { |
| String location = m.group(2); |
| |
| // Certain files can safely be skipped without marking the project |
| // as having errors. See isHidden() in AaptAssets.cpp: |
| String type = m.group(1); |
| if (type.equals("backup") //$NON-NLS-1$ // main.xml~, etc |
| || type.equals("hidden") //$NON-NLS-1$ // .gitignore, etc |
| || type.equals("index")) { //$NON-NLS-1$ // thumbs.db, etc |
| continue; |
| } |
| |
| // check the values and attempt to mark the file. |
| if (checkAndMark(location, null, p.trim(), osRoot, project, |
| AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) { |
| return true; |
| } |
| |
| // success, go to the next line |
| continue; |
| } |
| |
| m = sXmlBlockPattern.matcher(p); |
| if (m.matches()) { |
| // W/ResourceType(12345): Bad XML block: no root element node found |
| // Sadly there's NO filename reference; this error typically describes the |
| // error *after* this line. |
| if (results.length == 1) { |
| // This is the only error message: dump to console and quit |
| return true; |
| } |
| // Continue: the real culprit is displayed next and should get a marker |
| continue; |
| } |
| |
| 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; |
| } |
| } |
| |
| // Attempt to determine the exact range of characters affected by this error. |
| // This will look up the actual text of the file, go to the particular error line |
| // and scan for the specific string mentioned in the error. |
| int startOffset = -1; |
| int endOffset = -1; |
| if (f2 instanceof IFile) { |
| IRegion region = findRange((IFile) f2, line, message); |
| if (region != null) { |
| startOffset = region.getOffset(); |
| endOffset = startOffset + region.getLength(); |
| } |
| } |
| |
| // 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) { |
| if (startOffset != -1) { |
| int tmpBegin = marker.getAttribute(IMarker.CHAR_START, -1); |
| if (tmpBegin != startOffset) { |
| break; |
| } |
| int tmpEnd = marker.getAttribute(IMarker.CHAR_END, -1); |
| if (tmpEnd != startOffset) { |
| break; |
| } |
| } |
| |
| 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, |
| startOffset, endOffset, severity); |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Given an aapt error message in a given file and a given (initial) line number, |
| * return the corresponding offset range for the error, or null. |
| */ |
| private static IRegion findRange(IFile file, int line, String message) { |
| Matcher matcher = sValueRangePattern.matcher(message); |
| if (matcher.find()) { |
| String property = matcher.group(1); |
| String value = matcher.group(2); |
| |
| // First find the property. We can't just immediately look for the |
| // value, because there could be other attributes in this element |
| // earlier than the one in error, and we might accidentally pick |
| // up on a different occurrence of the value in a context where |
| // it is valid. |
| if (value.length() > 0) { |
| return findRange(file, line, property, value); |
| } else { |
| // Find first occurrence of property followed by '' or "" |
| IRegion region1 = findRange(file, line, property, "\"\""); //$NON-NLS-1$ |
| IRegion region2 = findRange(file, line, property, "''"); //$NON-NLS-1$ |
| if (region1 == null) { |
| if (region2 == null) { |
| // Highlight the property instead |
| return findRange(file, line, property, null); |
| } |
| return region2; |
| } else if (region2 == null) { |
| return region1; |
| } else if (region1.getOffset() < region2.getOffset()) { |
| return region1; |
| } else { |
| return region2; |
| } |
| } |
| } |
| |
| matcher = sRepeatedRangePattern.matcher(message); |
| if (matcher.find()) { |
| String property = matcher.group(2); |
| return findRange(file, line, property, null); |
| } |
| |
| matcher = sNoResourcePattern.matcher(message); |
| if (matcher.find()) { |
| String property = matcher.group(1); |
| return findRange(file, line, property, null); |
| } |
| |
| matcher = sRequiredPattern.matcher(message); |
| if (matcher.find()) { |
| String elementName = matcher.group(2); |
| IRegion region = findRange(file, line, '<' + elementName, null); |
| if (region != null && region.getLength() > 1) { |
| // Skip the opening < |
| region = new Region(region.getOffset() + 1, region.getLength() - 1); |
| } |
| return region; |
| } |
| |
| if (message.endsWith(ORIGINALLY_DEFINED_MSG)) { |
| return findLineTextRange(file, line); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Given a file and line number, return the range of the first match starting on the |
| * given line. If second is non null, also search for the second string starting at he |
| * location of the first string. |
| */ |
| private static IRegion findRange(IFile file, int line, String first, |
| String second) { |
| IRegion region = null; |
| IDocumentProvider provider = new TextFileDocumentProvider(); |
| try { |
| provider.connect(file); |
| IDocument document = provider.getDocument(file); |
| if (document != null) { |
| IRegion lineInfo = document.getLineInformation(line - 1); |
| int lineStartOffset = lineInfo.getOffset(); |
| // The aapt errors will be anchored on the line where the |
| // element starts - which means that with formatting where |
| // attributes end up on subsequent lines we don't find it on |
| // the error line indicated by aapt. |
| // Therefore, search forwards in the document. |
| FindReplaceDocumentAdapter adapter = |
| new FindReplaceDocumentAdapter(document); |
| |
| region = adapter.find(lineStartOffset, first, |
| true /*forwardSearch*/, true /*caseSensitive*/, |
| false /*wholeWord*/, false /*regExSearch*/); |
| if (region != null && second != null) { |
| region = adapter.find(region.getOffset() + first.length(), second, |
| true /*forwardSearch*/, true /*caseSensitive*/, |
| false /*wholeWord*/, false /*regExSearch*/); |
| } |
| } |
| } catch (Exception e) { |
| AdtPlugin.log(e, "Can't find range information for %1$s", file.getName()); |
| } finally { |
| provider.disconnect(file); |
| } |
| return region; |
| } |
| |
| /** Returns the non-whitespace line range at the given line number. */ |
| private static IRegion findLineTextRange(IFile file, int line) { |
| IDocumentProvider provider = new TextFileDocumentProvider(); |
| try { |
| provider.connect(file); |
| IDocument document = provider.getDocument(file); |
| if (document != null) { |
| IRegion lineInfo = document.getLineInformation(line - 1); |
| String lineContents = document.get(lineInfo.getOffset(), lineInfo.getLength()); |
| int lineBegin = 0; |
| int lineEnd = lineContents.length()-1; |
| |
| for (; lineEnd >= 0; lineEnd--) { |
| char c = lineContents.charAt(lineEnd); |
| if (!Character.isWhitespace(c)) { |
| break; |
| } |
| } |
| lineEnd++; |
| for (; lineBegin < lineEnd; lineBegin++) { |
| char c = lineContents.charAt(lineBegin); |
| if (!Character.isWhitespace(c)) { |
| break; |
| } |
| } |
| if (lineBegin < lineEnd) { |
| return new Region(lineInfo.getOffset() + lineBegin, lineEnd - lineBegin); |
| } |
| } |
| } catch (Exception e) { |
| AdtPlugin.log(e, "Can't find range information for %1$s", file.getName()); |
| } finally { |
| provider.disconnect(file); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * 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 != null && r.exists()) { |
| return r; |
| } |
| } |
| |
| return null; |
| } |
| |
| } |