blob: 1d5625dd7f1718545a1c24d42909e27b5596f84a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.internal.core.builders;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.text.*;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.PDECoreMessages;
import org.eclipse.pde.internal.core.builders.IncrementalErrorReporter.VirtualMarker;
public class JarManifestErrorReporter extends ErrorReporter {
/**
* Map of IHeader by name
*/
protected Map<String, JarManifestHeader> fHeaders;
protected IDocument fTextDocument;
public JarManifestErrorReporter(IFile file) {
super(file);
fTextDocument = createDocument(file);
}
private String getHeaderName(String line) {
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (c == ':') {
return line.substring(0, i);
}
if ((c < 'A' || 'Z' < c) && (c < 'a' || 'z' < c) && (c < '0' || '9' < c)) {
if (i == 0) {
return null;
}
if (c != '-' && c != '_') {
return null;
}
}
}
return null;
}
protected int getPackageLine(IHeader header, ManifestElement element) {
String packageName = element.getValue();
if (element.getDirectiveKeys() != null || element.getKeys() != null)
return getLine(header, packageName + ";"); //$NON-NLS-1$
// check for this exact package on the last line
try {
IRegion lineRegion = fTextDocument.getLineInformation(header.getLineNumber() + header.getLinesSpan() - 1);
String lineStr = fTextDocument.get(lineRegion.getOffset(), lineRegion.getLength());
if (lineStr.endsWith(packageName)) {
return header.getLineNumber() + header.getLinesSpan();
}
} catch (BadLocationException ble) {
PDECore.logException(ble);
}
// search all except last line
return getLine(header, packageName + ","); //$NON-NLS-1$
}
protected int getLine(IHeader header, String valueSubstring) {
for (int l = header.getLineNumber(); l < header.getLineNumber() + header.getLinesSpan(); l++) {
try {
IRegion lineRegion = fTextDocument.getLineInformation(l);
String lineStr = fTextDocument.get(lineRegion.getOffset(), lineRegion.getLength());
if (lineStr.indexOf(valueSubstring) >= 0) {
return l + 1;
}
} catch (BadLocationException ble) {
PDECore.logException(ble);
}
}
// it might span mutliple lines, try a longer algorithm
try {
IRegion lineRegion = fTextDocument.getLineInformation(header.getLineNumber());
String lineStr = fTextDocument.get(lineRegion.getOffset(), lineRegion.getLength());
for (int l = header.getLineNumber() + 1; l < header.getLineNumber() + header.getLinesSpan(); l++) {
lineRegion = fTextDocument.getLineInformation(l);
lineStr += fTextDocument.get(lineRegion.getOffset() + 1/* the space */, lineRegion.getLength());
if (lineStr.indexOf(valueSubstring) >= 0) {
return l;
}
}
} catch (BadLocationException ble) {
PDECore.logException(ble);
}
return header.getLineNumber() + 1;
}
/**
* @param document
*/
protected void parseManifest(IDocument document, IProgressMonitor monitor) {
try {
fHeaders = new HashMap<>();
JarManifestHeader header = null;
int l = 0;
for (; l < document.getNumberOfLines(); l++) {
if (l % 100 == 0)
checkCanceled(monitor);
IRegion lineInfo = document.getLineInformation(l);
String line = document.get(lineInfo.getOffset(), lineInfo.getLength());
// test lines' length
String lineDelimiter = document.getLineDelimiter(l);
if (lineDelimiter == null) {
lineDelimiter = ""; //$NON-NLS-1$
}
ByteBuffer byteBuf = StandardCharsets.UTF_8.encode(line);
if (byteBuf.limit() + lineDelimiter.length() > 512) {
report(PDECoreMessages.BundleErrorReporter_lineTooLong, l + 1, CompilerFlags.ERROR, PDEMarkerFactory.CAT_FATAL);
return;
}
// parse
if (line.length() == 0) {
// Empty Line
if (l == 0) {
report(PDECoreMessages.BundleErrorReporter_noMainSection, 1, CompilerFlags.ERROR, PDEMarkerFactory.CAT_FATAL);
return;
}
/* flush last line */
if (header != null) {
fHeaders.put(header.getName().toLowerCase(), header);
header = null;
}
break; /* done processing main attributes */
}
if (line.charAt(0) == ' ') {
// Continuation Line
if (l == 0) { /* if no previous line */
report(PDECoreMessages.BundleErrorReporter_noMainSection, 1, CompilerFlags.ERROR, PDEMarkerFactory.CAT_FATAL);
return;
}
if (header != null) {
header.append(line.substring(1));
}
continue;
}
// Expecting New Header
if (header != null) {
fHeaders.put(header.getName().toLowerCase(), header);
header = null;
}
int colon = line.indexOf(':');
if (colon == -1) { /* no colon */
report(PDECoreMessages.BundleErrorReporter_noColon, l + 1, CompilerFlags.ERROR, PDEMarkerFactory.CAT_FATAL);
return;
}
String headerName = getHeaderName(line);
if (headerName == null) {
report(PDECoreMessages.BundleErrorReporter_invalidHeaderName, l + 1, CompilerFlags.ERROR, PDEMarkerFactory.CAT_FATAL);
return;
}
if (line.length() < colon + 2 || line.charAt(colon + 1) != ' ') {
report(PDECoreMessages.BundleErrorReporter_noSpaceValue, l + 1, CompilerFlags.ERROR, PDEMarkerFactory.CAT_FATAL);
return;
}
if ("Name".equals(headerName)) { //$NON-NLS-1$
report(PDECoreMessages.BundleErrorReporter_nameHeaderInMain, l + 1, CompilerFlags.ERROR, PDEMarkerFactory.CAT_FATAL);
return;
}
header = new JarManifestHeader(headerName, line.substring(colon + 2), l, this);
if (fHeaders.containsKey(header.getName().toLowerCase())) {
report(PDECoreMessages.BundleErrorReporter_duplicateHeader, l + 1, CompilerFlags.WARNING, PDEMarkerFactory.CAT_OTHER);
}
}
if (header != null) {
// lingering header, line not terminated
VirtualMarker marker = report(PDECoreMessages.BundleErrorReporter_noLineTermination, l, CompilerFlags.ERROR, PDEMarkerFactory.M_NO_LINE_TERMINATION, PDEMarkerFactory.CAT_FATAL);
if (marker != null) {
// Check whether last line is purely whitespace, and add
// this information to the marker.
IRegion lineInfo = document.getLineInformation(document.getNumberOfLines() - 1);
String line = document.get(lineInfo.getOffset(), lineInfo.getLength());
marker.setAttribute(PDEMarkerFactory.ATTR_HAS_CONTENT, !line.matches("\\s+")); //$NON-NLS-1$
}
return;
}
// If there is any more headers, not starting with a Name header
// the empty lines are a mistake, report it.
for (; l < document.getNumberOfLines(); l++) {
IRegion lineInfo = document.getLineInformation(l);
String line = document.get(lineInfo.getOffset(), lineInfo.getLength());
if (line.length() == 0) {
continue;
}
if (!line.startsWith("Name:")) { //$NON-NLS-1$
report(PDECoreMessages.BundleErrorReporter_noNameHeader, l, CompilerFlags.ERROR, PDEMarkerFactory.CAT_FATAL);
}
break;
}
return;
} catch (BadLocationException ble) {
PDECore.logException(ble);
}
}
protected void reportIllegalAttributeValue(IHeader header, String key, String value) {
String msg = NLS.bind(PDECoreMessages.BundleErrorReporter_att_value, (new String[] {value, key}));
report(msg, getLine(header, key + "="), CompilerFlags.ERROR, //$NON-NLS-1$
PDEMarkerFactory.CAT_FATAL);
}
protected void reportIllegalValue(IHeader header, String value) {
String msg = NLS.bind(PDECoreMessages.BundleErrorReporter_illegal_value, value);
report(msg, getLine(header, value), CompilerFlags.ERROR, PDEMarkerFactory.CAT_FATAL);
}
protected void reportIllegalDirectiveValue(IHeader header, String key, String value) {
String msg = NLS.bind(PDECoreMessages.BundleErrorReporter_dir_value, (new String[] {value, key}));
report(msg, getLine(header, key + ":="), CompilerFlags.ERROR, PDEMarkerFactory.CAT_FATAL); //$NON-NLS-1$
}
protected void validateAttributeValue(IHeader header, ManifestElement element, String key, String[] allowedValues) {
String value = element.getAttribute(key);
if (value == null) {
return;
}
for (String allowedValue : allowedValues) {
if (allowedValue.equals(value)) {
return;
}
}
reportIllegalAttributeValue(header, key, value);
}
protected void validateBooleanAttributeValue(IHeader header, ManifestElement element, String key) {
validateAttributeValue(header, element, key, BOOLEAN_VALUES);
}
protected void validateBooleanDirectiveValue(IHeader header, ManifestElement element, String key) {
validateDirectiveValue(header, element, key, BOOLEAN_VALUES);
}
protected void validateBooleanValue(IHeader header) {
validateHeaderValue(header, BOOLEAN_VALUES);
}
@Override
protected void validate(IProgressMonitor monitor) {
if (fTextDocument != null)
parseManifest(fTextDocument, monitor);
}
protected void validateDirectiveValue(IHeader header, ManifestElement element, String key, String[] allowedValues) {
String value = element.getDirective(key);
if (value == null) {
return;
}
for (String allowedValue : allowedValues) {
if (allowedValue.equals(value)) {
return;
}
}
reportIllegalDirectiveValue(header, key, value);
}
protected void validateHeaderValue(IHeader header, String[] allowedValues) {
ManifestElement[] elements = header.getElements();
if (elements.length > 0) {
for (String allowedValue : allowedValues) {
if (allowedValue.equals(elements[0].getValue())) {
return;
}
}
reportIllegalValue(header, elements[0].getValue());
}
}
protected IHeader validateRequiredHeader(String name) {
IHeader header = fHeaders.get(name.toLowerCase());
if (header == null) {
report(NLS.bind(PDECoreMessages.BundleErrorReporter_headerMissing, name), 1, CompilerFlags.ERROR, PDEMarkerFactory.CAT_FATAL);
}
return header;
}
protected IHeader getHeader(String key) {
return fHeaders.get(key.toLowerCase());
}
protected void checkCanceled(IProgressMonitor monitor) throws OperationCanceledException {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}