/*******************************************************************************
* Copyright (c) 2007, 2016 Alphonse Van Assche.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alphonse Van Assche - initial API and implementation
*******************************************************************************/
package org.eclipse.linuxtools.internal.rpm.rpmlint.parser;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.linuxtools.internal.rpm.rpmlint.Activator;
import org.eclipse.linuxtools.internal.rpm.rpmlint.RpmlintLog;
import org.eclipse.linuxtools.internal.rpm.rpmlint.builder.RpmlintBuilder;
import org.eclipse.linuxtools.internal.rpm.rpmlint.preferences.PreferenceConstants;
import org.eclipse.linuxtools.internal.rpm.rpmlint.resolutions.RpmlintMarkerResolutionGenerator;
import org.eclipse.linuxtools.rpm.core.utils.Utils;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
/**
* Parser for rpmlint output.
*
*/
public class RpmlintParser {
private static final String COLON = ":"; //$NON-NLS-1$
private static final String SPACE = " "; //$NON-NLS-1$
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
/**
* Parse visited resources.
*
* @param visitedResources
* The list of resources to parse.
*
* @return a <code>RpmlintItem</code> ArrayList.
*/
public static List<RpmlintItem> parseVisisted(List<String> visitedResources) {
String rpmlintPath = new ScopedPreferenceStore(InstanceScope.INSTANCE,
Activator.PLUGIN_ID)
.getString(PreferenceConstants.P_RPMLINT_PATH);
/*
* It's fine to fail silently if rpmlint is not installed as the actual
* user messages and etc. are displayed by the ui code and this is just
* a guard if we have configuration changing or someone playing with the
* project files.
*/
if (visitedResources.isEmpty() || !Files.exists(Paths.get(rpmlintPath))) {
return new ArrayList<>();
}
return parseRpmlintOutput(runRpmlintCommand(visitedResources));
}
/**
* Adds a rpmlint marker.
*
* @param file
* The file to create the marker for.
* @param message
* The marker message.
* @param lineNumber
* The line at which the marker appears.
* @param charStart
* The index of the starting char for the marker.
* @param charEnd
* The index of the ending char for the marker.
* @param severity
* The marker seveirty.
* @param rpmlintID
* The id of the rpmlint warning/error.
* @param rpmlintrefferedContent
* Additional content reffered by the marker.
*/
public static void addMarker(IFile file, String message, int lineNumber,
int charStart, int charEnd, int severity, String rpmlintID,
String rpmlintrefferedContent) {
try {
IMarker marker = file.createMarker(RpmlintBuilder.MARKER_ID);
marker.setAttribute(IMarker.LOCATION, file.getFullPath().toString());
marker.setAttribute(IMarker.MESSAGE, message);
marker.setAttribute(IMarker.SEVERITY, severity);
marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
marker.setAttribute(IMarker.CHAR_START, charStart);
marker.setAttribute(IMarker.CHAR_END, charEnd);
marker.setAttribute(
RpmlintMarkerResolutionGenerator.RPMLINT_ERROR_ID,
rpmlintID);
marker.setAttribute(
RpmlintMarkerResolutionGenerator.RPMLINT_REFFERED_CONTENT,
rpmlintrefferedContent);
} catch (CoreException e) {
RpmlintLog.logError(e);
}
}
/**
* Adds a rpmlint marker.
*
* @param file
* The file to create the marker for.
* @param message
* The marker message.
* @param severity
* The marker severity.
* @param rpmlintID
* The id of the rpmlint warning/error.
* @param rpmlintrefferedContent
* Additional content referred by the marker.
*/
public static void addMarker(IFile file, String message, int severity,
String rpmlintID, String rpmlintrefferedContent) {
try {
IMarker marker = file.createMarker(RpmlintBuilder.MARKER_ID);
marker.setAttribute(IMarker.LOCATION, file.getFullPath().toString());
marker.setAttribute(IMarker.MESSAGE, message);
marker.setAttribute(IMarker.SEVERITY, severity);
marker.setAttribute(
RpmlintMarkerResolutionGenerator.RPMLINT_ERROR_ID,
rpmlintID);
marker.setAttribute(
RpmlintMarkerResolutionGenerator.RPMLINT_REFFERED_CONTENT,
rpmlintrefferedContent);
} catch (CoreException e) {
RpmlintLog.logError(e);
}
}
/**
* Clear the rpmlint specific markers.
*
* @param resource
* The resource for which to clean the marker.
*/
public static void deleteMarkers(IResource resource) {
try {
resource.deleteMarkers(RpmlintBuilder.MARKER_ID, false,
IResource.DEPTH_ZERO);
} catch (CoreException e) {
RpmlintLog.logError(e);
}
}
/**
* Parse a given rpmlint <code>InputStream</code>
*
* @param rpmlint
* <code>InputStream</code> to parse.
* @return a <code>RpmlintItem</code> ArrayList.
*/
private static List<RpmlintItem> parseRpmlintOutput(BufferedInputStream in) {
RpmlintItem item = new RpmlintItem();
ArrayList<RpmlintItem> rpmlintItems = new ArrayList<>();
LineNumberReader reader = new LineNumberReader(
new InputStreamReader(in));
String line;
boolean isFirtItemLine = true;
String[] lineItems;
String description = EMPTY_STRING;
try {
while ((line = reader.readLine()) != null) {
if (isFirtItemLine) {
isFirtItemLine = false;
lineItems = line.split(COLON, 4);
item.setFileName(lineItems[0]);
int lineNbr;
// FIXME: last rpmlint version (0.83) contain a summary
// line at the bottom of it output, so if we
// detected this line we can safely return rpmlintItems,
// maybe we can find a better way to detect this line.
try {
Integer.parseInt(line.split(SPACE)[0]);
return rpmlintItems;
} catch (NumberFormatException e) {
// this line is not the summary
}
// TODO: ask rpmlint upstream to display always the same
// output.
// at the moment the line number is not always displayed.
// If the same output is always used, all the workarounds
// for the line number can be
// removed.
try {
lineNbr = Integer.parseInt(lineItems[1]);
item.setSeverity(lineItems[2]);
lineItems = lineItems[3].trim().split(SPACE, 2);
} catch (NumberFormatException e) {
// No line number showed for this rpmlint warning.
lineItems = line.split(COLON, 3);
lineNbr = -1;
item.setSeverity(lineItems[1]);
lineItems = lineItems[2].trim().split(SPACE, 2);
}
item.setLineNbr(lineNbr);
item.setId(lineItems[0]);
if (lineItems.length > 1) {
// Maybe this error occur when rpmlint execute 'rpm -q
// --qf=
// --specfile file.spec' command
RpmlintItem tmpItem = parseRpmOutput(item, lineItems[1]);
if (tmpItem == null) {
item.setRefferedContent(lineItems[1]);
} else {
item = tmpItem;
}
} else {
item.setRefferedContent(EMPTY_STRING);
}
} else {
description += line + '\n';
}
if (line.equals(EMPTY_STRING)) {
if (item.getMessage() == null) {
item.setMessage(description.substring(0,
description.length() - 2));
}
int useOfTabsAndSpaces = getMixedUseOfTabsAndSpaces(item
.getRefferedContent());
if (useOfTabsAndSpaces != -1) {
item.setLineNbr(useOfTabsAndSpaces);
}
rpmlintItems.add(item);
item = new RpmlintItem();
// Reinitialize parser for the next item
isFirtItemLine = true;
description = EMPTY_STRING;
}
}
// Close the input stream
in.close();
} catch (IOException e) {
RpmlintLog.logError(e);
}
return rpmlintItems;
}
private static RpmlintItem parseRpmOutput(RpmlintItem item, String line) {
String[] rpmErrorItems = line.split(COLON, 4);
if (item.getId().equalsIgnoreCase("specfile-error")) { //$NON-NLS-1$
// set severity
item.setSeverity("E"); //$NON-NLS-1$
} else {
return null;
}
// set line number
try {
if (rpmErrorItems[1].matches(" line [0-9]+$")) { //$NON-NLS-1$
item.setLineNbr(Integer.parseInt(rpmErrorItems[1].replace(
" line ", ""))); //$NON-NLS-1$ //$NON-NLS-2$
item.setMessage(rpmErrorItems[2]);
item.setRefferedContent(rpmErrorItems[3]);
} else {
item.setLineNbr(-1);
item.setMessage(rpmErrorItems[1]);
item.setRefferedContent(""); //$NON-NLS-1$
}
} catch (NumberFormatException e) {
return null;
}
return item;
}
/**
* Run rpmlint command on given visitedResources.
*
* @param specContent
* The specfile content.
* @return The rpmlint command <code>InputStream</code>.
*/
private static BufferedInputStream runRpmlintCommand(
List<String> visitedResources) {
BufferedInputStream in = null;
int i = 2;
String[] cmd = new String[visitedResources.size() + i];
cmd[0] = new ScopedPreferenceStore(InstanceScope.INSTANCE,
Activator.PLUGIN_ID)
.getString(PreferenceConstants.P_RPMLINT_PATH);
cmd[1] = "-i"; //$NON-NLS-1$
for (String resource : visitedResources) {
cmd[i] = resource;
i++;
}
try {
in = Utils.runCommandToInputStream(cmd);
} catch (IOException e) {
// FIXME: rpmlint is not installed in the default place -> ask user
// to open the prefs page.
RpmlintLog.logError(e);
}
return in;
}
/**
*
* Return the line number for given specContent and strToFind, it returns -1
* if the string to find is not found.
*
* @param specContent
* The content of the spec file.
*
* @param strToFind
* The string we are looking for.
* @return The line number.
*/
public static int getRealLineNbr(String specContent, String strToFind) {
int ret = -1;
if (strToFind.isEmpty()) {
return ret;
}
String line;
LineNumberReader reader = new LineNumberReader(new StringReader(
specContent));
try {
while ((line = reader.readLine()) != null) {
if (line.replaceAll("\t| ", EMPTY_STRING).indexOf( //$NON-NLS-1$
strToFind.replaceAll("\t| ", EMPTY_STRING)) > -1) { //$NON-NLS-1$
ret = reader.getLineNumber();
}
}
} catch (IOException e) {
// return -1 if an I/O Exception occure.
}
return ret;
}
private static int getMixedUseOfTabsAndSpaces(String refferedContent) {
int lineNbr = -1;
if (refferedContent.indexOf("(spaces: line") > -1) { //$NON-NLS-1$
String tabsAndSpacesPref = new ScopedPreferenceStore(
InstanceScope.INSTANCE, Activator.PLUGIN_ID)
.getString(PreferenceConstants.P_RPMLINT_TABS_AND_SPACES);
String[] spacesAndTabs = refferedContent.split("line"); //$NON-NLS-1$
if (tabsAndSpacesPref == PreferenceConstants.P_RPMLINT_SPACES) {
lineNbr = Integer
.parseInt(spacesAndTabs[1].split(",")[0].trim()); //$NON-NLS-1$
} else {
lineNbr = Integer.parseInt(spacesAndTabs[2].replaceFirst(
"\\)", EMPTY_STRING).trim()); //$NON-NLS-1$
}
}
return lineNbr;
}
}