blob: 3460042c3e1b743c81503b249d783ffe56c51f32 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2013 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.api.tools.internal.tasks;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.TreeSet;
import java.util.regex.Pattern;
import org.apache.tools.ant.BuildException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.osgi.service.resolver.ResolverError;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
import org.eclipse.pde.api.tools.internal.search.ReferenceLookupVisitor;
import org.eclipse.pde.api.tools.internal.search.SkippedComponent;
import org.eclipse.pde.api.tools.internal.search.UseScanParser;
import org.eclipse.pde.api.tools.internal.util.FilteredElements;
import org.eclipse.pde.api.tools.internal.util.Util;
/**
* Ant task for performing analysis of an API use scan against an alterante
* target (migration candidate)
*/
public final class ApiMigrationTask extends CommonUtilsTask {
/**
* Set of project names that were not searched
*/
private TreeSet<SkippedComponent> notsearched = null;
/**
* The regex pattern to use to compose the scope
*/
private String scopepattern = null;
/**
* The regex pattern to use to compose the reference set of component ids
*/
private String referencepattern = null;
/**
* handle to the baseline install dir to delete after the scan completes
*/
private File baselinedir = null;
/**
* Location of the API use scan to resolve in the migration candidate. This
* can be the root directory containing an 'xml' sub directory or the 'xml'
* directory itself.
*/
private String scanLocation = null;
/**
* Set the location of the product you want to use as the migration
* candidate.
*
* <p>
* It can be a .zip, .jar, .tgz, .tar.gz file, or a directory that
* corresponds to the Eclipse installation folder. This is the directory is
* which you can find the Eclipse executable.
* </p>
*
* @param location the location for the migration candidate to consider
*/
public void setCandidate(String location) {
this.currentBaselineLocation = location;
}
/**
* Set the regular expression pattern used to build the scope of elements to
* search for references from in the product location.
*
* <p>
* The pattern must be a well-formatted regular expression as defined here:
* http://java.sun.com/j2se/1.4.2/docs/api/java/util/regex/Pattern.html
* </p>
*
* @param scopepattern
*/
public void setScopePattern(String scopepattern) {
this.scopepattern = scopepattern;
}
/**
* Set the regular expression pattern used to build the scope of elements to
* search for references to in the product location.
*
* <p>
* The pattern must be a well-formatted regular expression as defined here:
* http://java.sun.com/j2se/1.4.2/docs/api/java/util/regex/Pattern.html
* </p>
*
* @param referencepattern
*/
public void setReferencePattern(String referencepattern) {
this.referencepattern = referencepattern;
}
/**
* Set the output location where the reports will be generated.
*
* <p>
* Once the task is completed, reports are available in this directory using
* a structure similar to the filter root. A sub-folder is created for each
* component that has problems to be reported. Each sub-folder contains a
* file called "report.xml".
* </p>
*
* <p>
* A special folder called "allNonApiBundles" is also created in this folder
* that contains a xml file called "report.xml". This file lists all the
* bundles that are not using the API Tools nature.
* </p>
*
* @param reportlocation the given location for the reference baseline to
* analyze
*/
public void setReport(String reportlocation) {
this.reportLocation = reportlocation;
}
/**
* Set the location of an existing API use scan containing references to
* re-resolve in the migration candidate. This can be the root directory
* containing 'xml' and 'html' subdirectories or the 'xml' directory itself.
*
* @param scanLocation the location of an existing API use scan
*/
public void setUseScan(String scanLocation) {
this.scanLocation = scanLocation;
}
/**
* Set the debug value.
* <p>
* The possible values are: <code>true</code>, <code>false</code>
* </p>
* <p>
* Default is <code>false</code>.
* </p>
*
* @param debugValue the given debug value
*/
public void setDebug(String debugValue) {
this.debug = Boolean.toString(true).equals(debugValue);
}
/**
* @see org.eclipse.pde.api.tools.internal.tasks.UseTask#assertParameters()
*/
protected void assertParameters() throws BuildException {
if (this.reportLocation == null) {
StringWriter out = new StringWriter();
PrintWriter writer = new PrintWriter(out);
writer.println(NLS.bind(Messages.ApiUseTask_missing_report_location, new String[] { this.reportLocation }));
writer.flush();
writer.close();
throw new BuildException(String.valueOf(out.getBuffer()));
}
if (this.currentBaselineLocation == null) {
StringWriter out = new StringWriter();
PrintWriter writer = new PrintWriter(out);
writer.println(NLS.bind(Messages.ApiUseTask_missing_baseline_argument, new String[] { this.currentBaselineLocation }));
writer.flush();
writer.close();
throw new BuildException(String.valueOf(out.getBuffer()));
}
if (this.scanLocation == null) {
StringWriter out = new StringWriter();
PrintWriter writer = new PrintWriter(out);
writer.println(NLS.bind(Messages.ApiMigrationTask_missing_scan_location, new String[] { this.scanLocation }));
writer.flush();
writer.close();
throw new BuildException(String.valueOf(out.getBuffer()));
}
String xmlLocation = scanLocation;
File file = new File(xmlLocation);
if (!file.exists()) {
StringWriter out = new StringWriter();
PrintWriter writer = new PrintWriter(out);
writer.println(NLS.bind(Messages.ApiMigrationTask_scan_location_not_exist, new String[] { this.scanLocation }));
writer.flush();
writer.close();
throw new BuildException(String.valueOf(out.getBuffer()));
}
if (!file.isDirectory()) {
StringWriter out = new StringWriter();
PrintWriter writer = new PrintWriter(out);
writer.println(NLS.bind(Messages.ApiMigrationTask_scan_location_not_dir, new String[] { this.scanLocation }));
writer.flush();
writer.close();
throw new BuildException(String.valueOf(out.getBuffer()));
}
if (this.reportLocation.equals(scanLocation)) {
StringWriter out = new StringWriter();
PrintWriter writer = new PrintWriter(out);
writer.println(NLS.bind(Messages.ApiMigrationTask_scan_locatoin_same_as_report_location, new String[] { this.scanLocation }));
writer.flush();
writer.close();
throw new BuildException(String.valueOf(out.getBuffer()));
}
}
@Override
public void execute() throws BuildException {
assertParameters();
writeDebugHeader();
cleanReportLocation();
IApiBaseline baseline = getBaseline(CURRENT_BASELINE_NAME, this.currentBaselineLocation);
try {
String xmlLocation = scanLocation;
File file = new File(xmlLocation);
File nested = new File(file, "xml"); //$NON-NLS-1$
if (nested.exists() && nested.isDirectory()) {
file = nested;
}
ReferenceLookupVisitor lookup = new ReferenceLookupVisitor(baseline, this.reportLocation);
lookup.setAnalysisScope(scopepattern);
lookup.setTargetScope(referencepattern);
FilteredElements excludedElements = CommonUtilsTask.initializeFilteredElements(this.excludeListLocation, baseline, this.debug);
if (this.debug) {
System.out.println("===================================================================================="); //$NON-NLS-1$
System.out.println("Excluded elements list:"); //$NON-NLS-1$
System.out.println(excludedElements);
}
lookup.setExcludedElements(excludedElements);
FilteredElements includedElements = CommonUtilsTask.initializeFilteredElements(this.includeListLocation, baseline, this.debug);
if (this.debug) {
System.out.println("===================================================================================="); //$NON-NLS-1$
System.out.println("Included elements list:"); //$NON-NLS-1$
System.out.println(includedElements);
}
lookup.setIncludedElements(includedElements);
UseScanParser parser = new UseScanParser();
parser.parse(file.getAbsolutePath(), new NullProgressMonitor(), lookup);
} catch (CoreException ce) {
throw new BuildException(ce.getStatus().getMessage(), ce);
} catch (Exception e) {
throw new BuildException(e.getMessage(), e);
} finally {
if (baseline != null) {
baseline.dispose();
deleteBaseline(this.currentBaselineLocation, this.baselinedir);
}
}
}
/**
* Returns if we should add the given component to our search scope
*
* @param component
* @param pattern
* @param allowresolve
* @return true if the given component should be considered, false otherwise
* @throws CoreException
*/
boolean acceptComponent(IApiComponent component, Pattern pattern, boolean allowresolve) throws CoreException {
if (!allowresolve) {
ResolverError[] errors = component.getErrors();
if (errors != null) {
this.notsearched.add(new SkippedComponent(component.getSymbolicName(), component.getVersion(), errors));
return false;
}
}
if (component.isSystemComponent()) {
return false;
}
if (pattern != null) {
return pattern.matcher(component.getSymbolicName()).matches();
}
return true;
}
/**
* Prepares and creates and new baseline with the given name from the given
* location. The returned {@link IApiBaseline} is not checked for resolution
* errors or consistency. If <code>null</code> is passed in as a location
* <code>null</code> is returned.
*
* @param name the name to give to the baseline
* @param location the location the baseline should be prepared from. If
* <code>null</code> is passed in, <code>null</code> is returned
* @return a new {@link IApiBaseline} with the given name from the given
* location or <code>null</code> if the given location is
* <code>null</code>
*/
protected IApiBaseline getBaseline(String name, String location) {
if (location == null) {
return null;
}
// extract the baseline to examine
long time = 0;
if (this.debug) {
time = System.currentTimeMillis();
System.out.println("Preparing '" + name + "' baseline installation..."); //$NON-NLS-1$ //$NON-NLS-2$
}
File installdir = extractSDK(name, location);
if (this.debug) {
System.out.println("done in: " + (System.currentTimeMillis() - time) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$
time = System.currentTimeMillis();
}
// create the baseline to examine
if (this.debug) {
time = System.currentTimeMillis();
System.out.println("Creating '" + name + "' baseline..."); //$NON-NLS-1$ //$NON-NLS-2$
}
IApiBaseline baseline = createBaseline(name, installdir.getAbsolutePath(), this.eeFileLocation);
if (this.debug) {
System.out.println("done in: " + (System.currentTimeMillis() - time) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$
}
this.baselinedir = installdir;
return baseline;
}
/**
* Cleans the report location specified by the parameter
* {@link CommonUtilsTask#reportLocation}
*/
protected void cleanReportLocation() {
if (this.reportLocation == null) {
return;
}
long time = 0;
if (this.debug) {
time = System.currentTimeMillis();
System.out.println("Cleaning report location..."); //$NON-NLS-1$
}
File file = new File(this.reportLocation);
if (file.exists()) {
Util.delete(file);
}
if (this.debug) {
System.out.println("done in: " + (System.currentTimeMillis() - time) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
* Writes a general header of debug information iff the debug flag is set to
* true
*/
protected void writeDebugHeader() {
if (this.debug) {
System.out.println("Migration candidate to analyze : " + this.currentBaselineLocation); //$NON-NLS-1$
System.out.println("Report location : " + this.reportLocation); //$NON-NLS-1$
System.out.println("Scan location : " + this.scanLocation); //$NON-NLS-1$
if (this.scopepattern == null) {
System.out.println("No scope pattern defined - searching all bundles"); //$NON-NLS-1$
} else {
System.out.println("Scope pattern : " + this.scopepattern); //$NON-NLS-1$
}
if (this.referencepattern == null) {
System.out.println("No baseline pattern defined - reporting references to all bundles"); //$NON-NLS-1$
} else {
System.out.println("Baseline pattern : " + this.referencepattern); //$NON-NLS-1$
}
System.out.println("-----------------------------------------------------------------------------------------------------"); //$NON-NLS-1$
}
}
/**
* Set the exclude list location.
*
* <p>
* The exclude list is used to know what bundles should excluded from the
* xml report generated by the task execution. Lines starting with '#' are
* ignored from the excluded elements.
* </p>
* <p>
* The format of the exclude list file looks like this:
* </p>
*
* <pre>
* # DOC BUNDLES
* org.eclipse.jdt.doc.isv
* org.eclipse.jdt.doc.user
* org.eclipse.pde.doc.user
* org.eclipse.platform.doc.isv
* org.eclipse.platform.doc.user
* # NON-ECLIPSE BUNDLES
* com.ibm.icu
* com.jcraft.jsch
* javax.servlet
* javax.servlet.jsp
* ...
* </pre>
* <p>
* The location is set using an absolute path.
* </p>
*
* @param excludeListLocation the given location for the excluded list file
*/
public void setExcludeList(String excludeListLocation) {
this.excludeListLocation = excludeListLocation;
}
/**
* Set the include list location.
*
* <p>
* The include list is used to know what bundles should included from the
* xml report generated by the task execution. Lines starting with '#' are
* ignored from the included elements.
* </p>
* <p>
* The format of the include list file looks like this:
* </p>
*
* <pre>
* # DOC BUNDLES
* org.eclipse.jdt.doc.isv
* org.eclipse.jdt.doc.user
* org.eclipse.pde.doc.user
* org.eclipse.platform.doc.isv
* org.eclipse.platform.doc.user
* # NON-ECLIPSE BUNDLES
* com.ibm.icu
* com.jcraft.jsch
* javax.servlet
* javax.servlet.jsp
* ...
* </pre>
* <p>
* The location is set using an absolute path.
* </p>
*
* @param includeListLocation the given location for the included list file
*/
public void setIncludeList(String includeListLocation) {
this.includeListLocation = includeListLocation;
}
}