blob: fe61b8e52af247fb7fbc5eb915468e3de31cb981 [file] [log] [blame]
/**
* Copyright (c) 2018 Eclipse contributors and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*/
package org.eclipse.emf.releng;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.equinox.internal.p2.metadata.BasicVersion;
import org.eclipse.equinox.internal.p2.metadata.IRequiredCapability;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.internal.repository.tools.AbstractApplication;
import org.eclipse.equinox.p2.internal.repository.tools.CompositeRepositoryApplication;
import org.eclipse.equinox.p2.internal.repository.tools.MirrorApplication;
import org.eclipse.equinox.p2.internal.repository.tools.RepositoryDescriptor;
import org.eclipse.equinox.p2.internal.repository.tools.SlicingOptions;
import org.eclipse.equinox.p2.metadata.IArtifactKey;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.IProvidedCapability;
import org.eclipse.equinox.p2.metadata.IRequirement;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.metadata.VersionRange;
import org.eclipse.equinox.p2.query.IQueryResult;
import org.eclipse.equinox.p2.query.QueryUtil;
import org.eclipse.equinox.p2.repository.ICompositeRepository;
import org.eclipse.equinox.p2.repository.IRepository;
import org.eclipse.equinox.p2.repository.artifact.IArtifactDescriptor;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository;
import org.eclipse.equinox.p2.repository.artifact.spi.AbstractArtifactRepository;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
import org.eclipse.equinox.p2.repository.metadata.spi.AbstractMetadataRepository;
import org.osgi.framework.Bundle;
/**
* A utility class that uses p2's repository tools to manage the update sites for EMF's builds.
*
*/
@SuppressWarnings("restriction")
public class UpdateSiteGenerator
{
/**
* The root-relative location of the builds folder.
*/
private static final String RELATIVE_BUILDS_FOLDER = "modeling/emf/emf/builds";
/**
* The files and folders that comprise a simple update site.
*/
private static final List<String> UPDATE_SITE_CONTENT = Arrays.asList(
new String []{ "binary", "features", "plugins", "artifacts.jar", "artifacts.xml.xz", "content.jar", "content.xml.xz", "p2.index" });
/**
* The number of nightly builds to maintain in the nightly composite.
*/
public static final int RETAINED_NIGHTLY_BUILDS = 7;
/**
* The valid values for {@code publish.build.type}.
*/
public static final List<String> BUILD_TYPES = Arrays.asList(new String []{ "nightly", "milestone", "release" });
/**
* The prefix qualifier associated with the {@link #BUILD_TYPES}.
*/
public static final List<String> BUILD_TYPE_QUALIFIERS = Arrays.asList(new String []{ "N", "S", "" });
/**
* The value of {@code publish.download.root.folder} with normalized path segment separators.
*/
private static final String DOWLOAD_ROOT_FOLDER_PROPERTY = System.getProperty("publish.download.root.folder").replace('\\', '/');
/**
* The expected value of the {@link #DOWLOAD_ROOT_FOLDER} on the {@code build.eclipse.org} host.
*/
public static final String DOWNLOAD_ECLIPSE_ORG_FOLDER = "/home/data/httpd/download.eclipse.org/";
/**
* The root folder in which the update sites for EMF's builds are managed.
*/
public static final String DOWLOAD_ROOT_FOLDER = DOWLOAD_ROOT_FOLDER_PROPERTY == null || DOWLOAD_ROOT_FOLDER_PROPERTY.trim().length() == 0
? DOWNLOAD_ECLIPSE_ORG_FOLDER : (!DOWLOAD_ROOT_FOLDER_PROPERTY.endsWith("/") ? DOWLOAD_ROOT_FOLDER_PROPERTY + "/" : DOWLOAD_ROOT_FOLDER_PROPERTY);
/**
* The location of the builds folder with in the root folder.
*/
public static final String BUILDS_ROOT_FOLDER = DOWLOAD_ROOT_FOLDER + RELATIVE_BUILDS_FOLDER;
/**
* Returns the destination location at which to promote the give type of build with the given name.
*
* @param buildType the {@code publish.build.type}.
* @param name the name of the folder, i.e., the {@code publish.build.timestamp} or the {@link #getVersion(String) version}.
* @return the destination location at which to promote the give type of build with the given name.
*
* @throws Exception
*/
public String getPromoteUpdateSiteDestination(String buildType, String name) throws Exception
{
File destinationBuildsTypeFolder = new File(BUILDS_ROOT_FOLDER, buildType);
return new File(destinationBuildsTypeFolder, getTargetFolder(buildType, name)).getCanonicalPath().replace('\\', '/');
}
/**
* Mirrors the source repository location to the destination location.
* This is used for nightly and milestone promotion.
*
* @param source the source repository.
* @param buildType the {@code publish.build.type}.
* @param buildTimestamp the {@code publish.build.timestamp}.
* @return the destination location at the repository was promoted.
* @throws Exception
*/
public File promoteUpdateSite(final String source, final String buildType, String buildTimestamp) throws Exception
{
final String destination = getPromoteUpdateSiteDestination(buildType, buildTimestamp);
return mirrorUpdateSite(source, destination, buildType);
}
/**
* Mirrors the source repository location to the destination location.
* The folder name is determined by the {@link #getVersion(String) version} of the source.
* This is used for release promotion.
*
* @param source the source repository.
* @param buildType the {@code publish.build.type}.
* @return the destination location at the repository was promoted.
* @throws Exception
*/
public File mirrorUpdateSite(final String source, final String buildType) throws Exception
{
String version = getVersion(source);
final String destination = getPromoteUpdateSiteDestination(buildType, version);
return mirrorUpdateSite(source, destination, buildType);
}
/**
* Mirrors the source repository to the destination repository.
* The name of the repository is computed from the content metadata of the source and from the build type,
* the <a href="https://wiki.eclipse.org/Equinox/p2/p2.mirrorsURL">p2.mirrorsURL</a> property configured,
* and {@code .xml.xz} formats are produced along with a {@code p2.index}.
*
* @param source the source repository location.
* @param destination the destination repository location.
* @param buildType the {@code publish.build.type}.
* @return the destination location at the repository was mirrored.
* @throws Exception
*/
private File mirrorUpdateSite(final String source, final String destination, final String buildType) throws Exception
{
MirrorApplication mirrorApplication = new MirrorApplication()
{
@Override
protected void finalizeRepositories()
{
if (destinationMetadataRepository instanceof AbstractMetadataRepository)
{
String repositoryName = getRepositoryName(destinationMetadataRepository) + ' ' + getLabel(buildType);
destinationMetadataRepository.setProperty(IRepository.PROP_NAME, repositoryName);
if (destinationArtifactRepository instanceof AbstractArtifactRepository)
{
destinationArtifactRepository.setProperty(IRepository.PROP_NAME, repositoryName);
if (destination.startsWith(DOWLOAD_ROOT_FOLDER))
{
String mirrorsURL = "http://www.eclipse.org/downloads/download.php?file=/" + destination.substring(DOWLOAD_ROOT_FOLDER.length());
destinationArtifactRepository.setProperty("p2.mirrorsURL", mirrorsURL);
}
}
}
super.finalizeRepositories();
}
};
File artifacts = new File(destination, "artifacts.xml.xz");
if (artifacts.isFile())
{
artifacts.delete();
}
File content = new File(destination, "content.xml.xz");
if (content.isFile())
{
content.delete();
}
File p2Index = new File(destination, "p2.index");
if (p2Index.isFile())
{
p2Index.delete();
}
mirrorApplication.initializeFromArguments(new String []{ "-source", source, "-destination", destination, "-writeMode", "clean" });
SlicingOptions slicingOptions = new SlicingOptions();
slicingOptions.latestVersionOnly(true);
mirrorApplication.setSlicingOptions(slicingOptions);
IStatus status = mirrorApplication.run(new NullProgressMonitor());
if (!status.isOK())
{
throw new CoreException(status);
}
xzCompress(destination);
return new File(destination);
}
/**
* Returns the version of the {@code org.eclipse.emf.sdk.feature.group} installable unit in the target repository.
* @param targetRepository the repository location.
* @return the associated semantic version of the repository.
* @throws Exception
*/
public String getVersion(String targetRepository) throws Exception
{
RepositoryAnalyzer repositoryAnalyzer = new RepositoryAnalyzer();
RepositoryDescriptor repositoryDescriptor = new RepositoryDescriptor();
repositoryDescriptor.setLocation(createURI(targetRepository));
repositoryAnalyzer.addSource(repositoryDescriptor);
String version = repositoryAnalyzer.getVersion();
return version;
}
/**
* Creates and returns the repository analyzer for the give repositories.
* @param repositories a list of repository location.
*
* @return the repository analyzer.
*/
public RepositoryAnalyzer getRepositoryAnalyzer(List<String> repositories)
{
RepositoryAnalyzer repositoryAnalyzer = new RepositoryAnalyzer();
RepositoryDescriptor repositoryDescriptor = new RepositoryDescriptor();
for (String repository : repositories)
{
repositoryDescriptor.setLocation(createURI(repository));
repositoryAnalyzer.addSource(repositoryDescriptor);
}
return repositoryAnalyzer;
}
/**
* Returns the title case label for the give build type.
* @param buildType the {@code publish.build.type}.
* o
* @return the title case label for the give build type.
*/
private String getLabel(String buildType)
{
return Character.toUpperCase(buildType.charAt(0)) + buildType.substring(1);
}
/**
* Returns the computed name for the repository.
* This will be 'EMF' followed the range of versions of the {@code org.eclipse.emf.sdk.feature.group} installable units in the repository.
* If there is only one version then it will be followed by only that one version.
* If there are none, then it's just 'EMF'.
* @param repository
* @return the computed name for the repository.
*/
private String getRepositoryName(IMetadataRepository repository)
{
IQueryResult<IInstallableUnit> groups = repository.query(QueryUtil.createIUQuery("org.eclipse.emf.sdk.feature.group"), new NullProgressMonitor());
List<BasicVersion> versions = new ArrayList<BasicVersion>();
for (Iterator<IInstallableUnit> i = groups.iterator(); i.hasNext();)
{
IInstallableUnit group = i.next();
Version iuVersion = group.getVersion();
if (iuVersion.isOSGiCompatible() && iuVersion instanceof BasicVersion)
{
BasicVersion basicVersion = (BasicVersion)iuVersion;
versions.add(basicVersion);
}
}
if (versions.isEmpty())
{
return "EMF";
}
else if (versions.size() == 1)
{
BasicVersion version = versions.get(0);
return "EMF " + version.getMajor() + "." + version.getMinor();
}
else
{
Collections.sort(versions);
BasicVersion minVersion = versions.get(0);
BasicVersion maxVersion = versions.get(versions.size() - 1);
return "EMF " + minVersion.getMajor() + "." + minVersion.getMinor() + "-" + maxVersion.getMajor() + "." + maxVersion.getMinor();
}
}
/**
* Returns the destination folder for the given build type and whether it is a latest composite or not.
*
* @param buildType the {@code publish.build.type}.
* @param latest whether this is a composite for the latest build.
* @return the destination folder.
*
* @throws Exception
*/
public String getCompositeUpdateSiteDestination(String buildType, boolean latest) throws Exception
{
File destinationBuildsTypeFolder = new File(BUILDS_ROOT_FOLDER, buildType);
if (latest)
{
destinationBuildsTypeFolder = new File(destinationBuildsTypeFolder, "latest");
}
return getCanonicalPath(destinationBuildsTypeFolder);
}
/**
* Creates a composite update that references the given source repositories, at a location determined by the build type and the latest indicator.
* @param sources the source repositories.
* @param buildType the {@code publish.build.type}.
* @param latest whether is is a composite refering to the latest.
*
* @throws Exception
*/
public String composeUpdateSites(List<String> sources, final String buildType, final boolean latest) throws Exception
{
String destination = getCompositeUpdateSiteDestination(buildType, latest);
final URI destinationURI = createURI(destination);
// We must set the user dir to ensure that we produce a composite that uses relative URIs!
//
System.setProperty("user.dir", new File(destination).getPath());
CompositeRepositoryApplication compositeRepositoryApplication = new CompositeRepositoryApplication()
{
@Override
protected void finalizeRepositories()
{
// Compute an appropriate name for the repository after it has been populated.
if (destinationMetadataRepository instanceof ICompositeRepository<?>)
{
String repositoryName = getRepositoryName(destinationMetadataRepository) + ' ' + getLabel(buildType) + (latest ? " Latest" : " Composite");
destinationMetadataRepository.setProperty(IRepository.PROP_NAME, repositoryName);
save((ICompositeRepository<?>)destinationMetadataRepository);
if (destinationArtifactRepository instanceof ICompositeRepository<?>)
{
destinationArtifactRepository.setProperty(IRepository.PROP_NAME, repositoryName);
save((ICompositeRepository<?>)destinationArtifactRepository);
}
}
super.finalizeRepositories();
}
private void save(ICompositeRepository<?> repository)
{
// Unfortunately p2 provides no API for saving the repository after changing the name.
// The repository is saved only when children are added or removed.
// So this a good opportunity to double check that all the children are usin a relative URI.
List<URI> children = repository.getChildren();
repository.removeAllChildren();
for (URI child : children)
{
URI relativeSourceURI = relativize(child, destinationURI);
if (relativeSourceURI == child)
{
throw new IllegalArgumentException("The URI '" + child + "' cannot be made relative to '" + destinationURI + "'");
}
repository.addChild(relativeSourceURI);
}
}
};
compositeRepositoryApplication.setRemoveAll(true);
RepositoryDescriptor destinationRepositoryDescriptor = new RepositoryDescriptor();
destinationRepositoryDescriptor.setLocation(destinationURI);
compositeRepositoryApplication.addDestination(destinationRepositoryDescriptor);
for (String source : sources)
{
RepositoryDescriptor childRepositoryDescriptor = new RepositoryDescriptor();
URI sourceURI = createURI(source);
URI relativeSourceURI = relativize(sourceURI, destinationURI);
if (relativeSourceURI == sourceURI)
{
// We must use relative URIs!
throw new IllegalArgumentException("The URI '" + sourceURI + "' cannot be made relative to '" + destinationURI + "'");
}
childRepositoryDescriptor.setLocation(relativeSourceURI);
compositeRepositoryApplication.addChild(childRepositoryDescriptor);
}
IStatus status = compositeRepositoryApplication.run(new NullProgressMonitor());
if (!status.isOK())
{
throw new CoreException(status);
}
return destination;
}
/**
* Returns the relative URI that the target can use to reference the source.
* It would be better to use EMF's URI.
*
* @param sourceURI the source URI.
* @param targetURI the target URI.
* @return the relative URI.
*/
private static URI relativize(URI sourceURI, URI targetURI)
{
URI relativeSourceURI = targetURI.relativize(sourceURI);
if (relativeSourceURI == sourceURI)
{
URI parentTargetURI = targetURI.resolve("..");
if (parentTargetURI != targetURI)
{
URI parentRelativeSourceURI = relativize(relativeSourceURI, parentTargetURI);
if (parentRelativeSourceURI != relativeSourceURI)
{
String string = parentRelativeSourceURI.toString();
int index = string.indexOf('/');
relativeSourceURI = URI.create(".." + string.substring(index));
}
}
}
return relativeSourceURI;
}
/**
* Produces the {@code .xml.zx} forms as well as the {@code p2.index}.
*
* @param targetRepository
*/
private void xzCompress(String targetRepository)
{
try
{
// We do this reflectively so that we can compile against Helios.
//
Bundle bundle = Platform.getBundle("org.eclipse.equinox.p2.repository.tools");
Class<?> xzCompressorClass = bundle.loadClass("org.eclipse.equinox.p2.internal.repository.tools.XZCompressor");
Object xzCompressor = xzCompressorClass.getDeclaredConstructor().newInstance();
xzCompressorClass.getMethod("setRepoFolder", String.class).invoke(xzCompressor, targetRepository);
xzCompressorClass.getMethod("compressRepo").invoke(xzCompressor);
}
catch (Exception exception)
{
// Ignore.
}
}
/**
* Returns the qualified target folder depending on the build type and the give target subfolder.
*
* @param buildType the {@code publis.build.type}.
* @param targetSubfolder the target subfolder.
* @return the qualified target folder depending on the build type and the give target subfolder.
*/
private String getTargetFolder(String buildType, String targetSubfolder)
{
return BUILD_TYPE_QUALIFIERS.get(BUILD_TYPES.indexOf(buildType)) + targetSubfolder;
}
public static URI createURI(String path)
{
return createURI(new File(path));
}
public static URI createURI(File file)
{
try
{
// Java has a bad habit of adding a trailing "/" if the file exists as a directory.
// We never want that because it gives different results depending on existence.
URI uri = file.getCanonicalFile().toURI();
String literal = uri.toString();
return literal.endsWith("/") ? new URI(literal.substring(0, literal.length() - 1)) : uri;
}
catch (Exception exception)
{
throw new IllegalArgumentException(exception);
}
}
/**
* Generates the index.html for the target repository.
* @param target the target repository.
*
* @throws Exception
*/
public void generateIndex(String target) throws Exception
{
String indexHTML = new UpdateSiteIndex().generate(new UpdateSiteIndexGenerator(target));
FileOutputStream out = new FileOutputStream(new File(target, "index.html"));
try
{
byte[] bytes = indexHTML.getBytes("UTF-8");
out.write(bytes);
}
finally
{
out.close();
}
File targetFile = new File(target);
if (targetFile.isDirectory())
{
for (File child : targetFile.listFiles())
{
if (child.isDirectory() && (new File(child, "compositeContent.jar").isFile() || new File(child, "content.jar").isFile()))
{
generateIndex(child.getPath());
}
}
}
}
/**
* Generates a zip and SHA hashes for all simple repositories nested in the target folder.
*
* @param target the target folder.
* @throws Exception
*/
public void generateDownloads(String target) throws Exception
{
File targetFile = new File(target);
if (targetFile.isDirectory())
{
if (new File(targetFile, "content.jar").isFile())
{
File archiveFile = getArchiveFile(target);
if (!archiveFile.exists())
{
createArchive(target);
}
if (!getDigestFile(archiveFile.getPath(), "SHA-256").isFile())
{
createDigest(archiveFile, "SHA-256");
}
if (!getDigestFile(archiveFile.getPath(), "SHA-512").isFile())
{
createDigest(archiveFile, "SHA-512");
}
}
else
{
for (File child : targetFile.listFiles())
{
if (child.isDirectory())
{
generateDownloads(child.getPath());
}
}
}
}
}
/**
* Sorts the list of repository folders to ensure that they are semantically order.
*
* @param repositories the repository folders.
*/
public static void sort(List<String> repositories)
{
Map<Long, String> orderedRepositories = new TreeMap<Long, String>();
for (String repository : repositories)
{
String name = new File(repository).getName();
if ("release".equals(name))
{
orderedRepositories.put(0L, repository);
}
else if ("milestone".equals(name))
{
orderedRepositories.put(1L, repository);
}
else if ("nightly".equals(name))
{
orderedRepositories.put(2L, repository);
}
else if ("latest".equals(name))
{
orderedRepositories.put(Long.MIN_VALUE, repository);
}
else if (name.charAt(0) == 'N' || name.charAt(0) == 'S')
{
orderedRepositories.put(-Long.parseLong(name.substring(1)), repository);
}
else if (name.startsWith("2."))
{
orderedRepositories.put(-Long.parseLong(name.substring(2)), repository);
}
else
{
throw new IllegalArgumentException(repository);
}
}
repositories.clear();
repositories.addAll(orderedRepositories.values());
}
/**
* Returns the {@link File#getCanonicalPath() canonical path} with normalized path separators, i.e., always '/'.
* @param file the file.
* @return the canonical path.
*
* @throws IOException
*/
public static String getCanonicalPath(File file) throws IOException
{
return file.getCanonicalPath().replace('\\', '/');
}
/**
* Returns the name of the zipped archive for the give simple repository.
*
* @param repository the simple repository.
* @return
*/
public static File getArchiveFile(String repository)
{
File file = new File(repository);
String name = "EMF-Updates-" + file.getName() + ".zip";
return new File(file, name);
}
/**
* Creates a zip archive for the simple repository.
*
* @param repository the simple repository
* @return the created archive.
*
* @throws IOException
*/
public static File createArchive(String repository) throws IOException
{
File archiveFile = getArchiveFile(repository);
File folder = archiveFile.getParentFile();
if (!folder.isDirectory() || !new File(folder, "content.jar").isFile())
{
throw new IllegalStateException(repository + "is not a valid p2 repository");
}
boolean delete = false;
FileOutputStream fileOutputStream = null;
ZipOutputStream zipOutputStream = null;
try
{
fileOutputStream = new FileOutputStream(archiveFile);
zipOutputStream = new ZipOutputStream(fileOutputStream);
for (File file : folder.listFiles())
{
String name = file.getName();
if (UPDATE_SITE_CONTENT.contains(name))
{
visit(zipOutputStream, folder, name);
}
}
}
catch (IOException exception)
{
delete = true;
}
finally
{
if (zipOutputStream != null)
{
zipOutputStream.close();
}
if (fileOutputStream != null)
{
fileOutputStream.close();
}
if (delete)
{
archiveFile.delete();
}
}
return archiveFile;
}
/**
* Visits the folders recursively to zip all files.
*
* @param zipOutputStream the target archive.
* @param root the root at which we started visiting.
* @param path the relative path we are currently visiting.
*
* @throws IOException
*/
private static void visit(ZipOutputStream zipOutputStream, File root, String path) throws IOException
{
File file = new File(root, path);
if (file.isFile())
{
ZipEntry zipEntry = new ZipEntry(path);
zipOutputStream.putNextEntry(zipEntry);
FileInputStream fileInputStream = null;
try
{
fileInputStream = new FileInputStream(file);
byte[] bytes = new byte [10000];
for (int length = fileInputStream.read(bytes); length != -1; length = fileInputStream.read(bytes))
{
zipOutputStream.write(bytes, 0, length);
}
zipOutputStream.closeEntry();
}
finally
{
if (fileInputStream != null)
{
fileInputStream.close();
}
}
}
else
{
for (String name : file.list())
{
visit(zipOutputStream, root, path + "/" + name);
}
}
}
/**
* Returns the file location at which a digest for the given algorithm will be generated.
* @param target the target file for the digest.
* @param algorithm the algorithm used to compute the digest.
* @return
*/
public static File getDigestFile(String target, String algorithm)
{
File file = new File(target);
File result = new File(file.getParentFile(), file.getName() + "." + algorithm.toLowerCase().replaceAll("-", ""));
return result;
}
/**
* Creates a file containing the digest for the given file using the given algorithm.
*
* @param file the file to digest.
* @param algorithm the algorithm to use for digesting.
* @return the location of the digest file.
*
* @throws IOException
*/
private static File createDigest(File file, String algorithm) throws IOException
{
File result = getDigestFile(file.getPath(), algorithm);
try
{
MessageDigest instance = MessageDigest.getInstance(algorithm);
FileInputStream in = null;
FileOutputStream out = null;
try
{
in = new FileInputStream(file);
byte[] bytes = new byte [10000];
for (int length = in.read(bytes); length != -1; length = in.read(bytes))
{
instance.update(bytes, 0, length);
}
byte[] digest = instance.digest();
StringBuilder body = new StringBuilder();
for (int i = 0; i < digest.length; ++i)
{
body.append(Integer.toHexString((digest[i] & 0xFF) | 0x100).substring(1, 3));
}
body.append(" *");
body.append(file.getName());
out = new FileOutputStream(result);
out.write(body.toString().getBytes("UTF-8"));
}
finally
{
if (in != null)
{
in.close();
}
if (out != null)
{
out.close();
}
}
}
catch (NoSuchAlgorithmException exception)
{
throw new IllegalArgumentException(exception);
}
return result;
}
/**
* A utility class used to load a repository in order to analyze its contents.
*/
public static class RepositoryAnalyzer extends AbstractApplication
{
/**
* The pattern for finding the commit ID in a branding plugin's {@code about.mappings}.
*/
private static final Pattern COMMIT_ID_PATTERN = Pattern.compile("^1=(.*)$", Pattern.MULTILINE);
/**
* The pattern for a well-formed Git commit ID.
*/
private static final Pattern VALID_COMMIT_ID_PATTERN = Pattern.compile("^([0-9a-fA-F]+)$");
/**
* The pattern for finding the commit ID in a branding plugin's {@code about.mappings}.
*/
private static final Pattern BUILD_ID_PATTERN = Pattern.compile("^0=(.*)$", Pattern.MULTILINE);
/**
* The pattern for a well-formed build ID.
*/
private static final Pattern VALID_BUILD_ID_PATTERN = Pattern.compile("^[MNRS]?(\\d{12})$");
/**
* The pattern for finding a child location in a {@code compositeContent.xml}.
*/
private static final Pattern CHILD_LOCATION_PATTERN = Pattern.compile("<child location='([^']*)'");
/**
* Returns the repository name.
*
* @return the repository name.
*/
public String getName()
{
return getMetadataRepository().getName();
}
/**
* Returns the two-segment version of the largest version of the {@code org.eclipse.emf.sdk.feature.group} in the repository.
*
* @return the version of the EMF SDK.
*
* @throws ProvisionException
*/
public String getVersion() throws ProvisionException
{
IMetadataRepository repository = getCompositeMetadataRepository();
IQueryResult<IInstallableUnit> query = repository.query(QueryUtil.createIUQuery("org.eclipse.emf.sdk.feature.group"), new NullProgressMonitor());
BasicVersion maxVersion = null;
for (Iterator<IInstallableUnit> i = query.iterator(); i.hasNext();)
{
IInstallableUnit iu = i.next();
Version version = iu.getVersion();
if (version instanceof BasicVersion)
{
BasicVersion basicVersion = (BasicVersion)version;
if (maxVersion == null || maxVersion.compareTo(basicVersion) < 0)
{
maxVersion = basicVersion;
}
}
}
return maxVersion.getMajor() + "." + maxVersion.getMinor();
}
/**
* Returns the list of child locations in the composite, or {@code null}, if the repository isn't a composite.
* The URIs in the repository must be file locations and those are the locations returned.
*
* @return the list of child locations in the composite, or {@code null}, if the repository isn't a composite.
*/
public List<String> getChildren()
{
IMetadataRepository metadataRepository = getMetadataRepository();
URI location = metadataRepository.getLocation();
File file = new File(new File(location), "compositeContent.jar");
if (file.isFile() && metadataRepository instanceof ICompositeRepository<?>)
{
List<String> result = new ArrayList<String>();
ICompositeRepository<?> compositeRepository = (ICompositeRepository<?>)metadataRepository;
List<URI> children = compositeRepository.getChildren();
for (URI uri : children)
{
try
{
result.add(getCanonicalPath(new File(uri)));
}
catch (IOException exception)
{
throw new IllegalStateException("The child '" + uri + "' of '" + location + "' is not a file in the file system.", exception);
}
}
return result;
}
else
{
return null;
}
}
/**
* Returns a list of the actual value of each child element's location attribute in the composite, or {@code null} if the repository is not a composite.
*
* @return a list of the actual value of each child element's location attribute in the composite, or {@code null} if the repository is not a composite.
*/
public List<String> getRawChildren()
{
IMetadataRepository metadataRepository = getMetadataRepository();
URI location = metadataRepository.getLocation();
File file = new File(new File(location), "compositeContent.jar");
if (file.isFile())
{
List<String> result = new ArrayList<String>();
ZipFile zipFile = null;
try
{
zipFile = new ZipFile(file);
ZipEntry entry = zipFile.getEntry("compositeContent.xml");
InputStream inputStream = zipFile.getInputStream(entry);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] bytes = new byte [10000];
for (int length = inputStream.read(bytes); length != -1; length = inputStream.read(bytes))
{
out.write(bytes, 0, length);
}
String content = new String(bytes, "UTF-8");
for (Matcher matcher = CHILD_LOCATION_PATTERN.matcher(content); matcher.find();)
{
result.add(matcher.group(1));
}
}
catch (Exception exception)
{
throw new IllegalStateException("Problems with " + file, exception);
}
finally
{
try
{
zipFile.close();
}
catch (IOException exception)
{
throw new IllegalStateException("Problems closing" + file, exception);
}
}
return result;
}
else
{
return null;
}
}
/**
* Returns the sorted list of all the SDK features in the repository.
* @return the sorted list of all the SDK features in the repository.
*/
public List<String> getSDKs()
{
List<String> result = new ArrayList<String>();
IMetadataRepository repository = getCompositeMetadataRepository();
IQueryResult<IInstallableUnit> query = repository.query(QueryUtil.createIUGroupQuery(), new NullProgressMonitor());
for (Iterator<IInstallableUnit> i = query.iterator(); i.hasNext();)
{
IInstallableUnit iu = i.next();
if (iu.getId().endsWith(".sdk.feature.group"))
{
String name = iu.getProperty("org.eclipse.equinox.p2.name", null);
if (!result.contains(name))
{
result.add(name);
}
}
}
Collections.sort(result);
return result;
}
/**
* Returns a sorted list of all the features in the repository.
* @return a sorted list of all the features in the repository.
*/
public List<String> getFeatures()
{
List<String> result = new ArrayList<String>();
IMetadataRepository repository = getCompositeMetadataRepository();
IQueryResult<IInstallableUnit> query = repository.query(QueryUtil.createIUGroupQuery(), new NullProgressMonitor());
for (Iterator<IInstallableUnit> i = query.iterator(); i.hasNext();)
{
IInstallableUnit iu = i.next();
if (!iu.getId().endsWith(".source.feature.group"))
{
String name = iu.getProperty("org.eclipse.equinox.p2.name", null);
name += " " + iu.getVersion();
name = name.substring(0, name.lastIndexOf('.'));
if (!result.contains(name))
{
result.add(name);
}
}
}
Collections.sort(result);
return result;
}
/**
* Returns a map from bundle name to a list of information for that bundle for each bundle in the repository.
* @return a map from bundle name to a list of information for that bundle for each bundle in the repository.
*/
public Map<String, List<String>> getBundles()
{
Map<String, List<String>> result = new TreeMap<String, List<String>>();
IMetadataRepository repository = getCompositeMetadataRepository();
IQueryResult<IInstallableUnit> query = repository.query(QueryUtil.createIUAnyQuery(), new NullProgressMonitor());
for (Iterator<IInstallableUnit> i = query.iterator(); i.hasNext();)
{
IInstallableUnit iu = i.next();
String id = iu.getId();
if (!id.endsWith(".source"))
{
List<String> lines = new ArrayList<String>();
for (IProvidedCapability providedCapability : iu.getProvidedCapabilities())
{
String namespace = providedCapability.getNamespace();
String name = providedCapability.getName();
if ("org.eclipse.equinox.p2.eclipse.type".equals(namespace) && "bundle".equals(name))
{
String iuName = iu.getProperty("org.eclipse.equinox.p2.name", null);
iuName += " " + iu.getVersion();
iuName = iuName.substring(0, iuName.lastIndexOf('.'));
if (!result.containsKey(iuName))
{
lines.add(0, "\u21D6 " + id + " " + iu.getVersion());
result.put(iuName, lines);
}
}
else if ("java.package".equals(namespace))
{
Version version = providedCapability.getVersion();
lines.add("\u2196 " + name + (Version.emptyVersion.equals(version) ? "" : " " + version));
}
}
for (IRequirement requirement : iu.getRequirements())
{
if (requirement instanceof IRequiredCapability)
{
IRequiredCapability requiredCapability = (IRequiredCapability)requirement;
String namespace = requiredCapability.getNamespace();
String line = null;
if ("osgi.bundle".equals(namespace))
{
line = "\u21D8 ";
}
else if ("java.package".equals(namespace))
{
line = "\u2198 ";
}
if (line != null)
{
String name = requiredCapability.getName();
VersionRange range = requiredCapability.getRange();
line += name;
if (!VersionRange.emptyRange.equals(range))
{
line += " " + range;
}
if (requiredCapability.getMin() == 0)
{
line += " optional";
if (requiredCapability.isGreedy())
{
line += " greedy";
}
}
lines.add(line);
}
}
}
}
}
return result;
}
/**
* Returns a map from project name to the URL for the commit ID URL in that project's branding plugin.
* @return a map from project name to the URL for the commit ID URL in that project's branding plugin.
*/
public Map<String, String> getCommits()
{
Map<String, String> result = new LinkedHashMap<String, String>();
getIDs(result, "org.eclipse.emf", COMMIT_ID_PATTERN, VALID_COMMIT_ID_PATTERN, "http://git.eclipse.org/c/emf/org.eclipse.emf.git/commit/?id=");
getIDs(result, "org.eclipse.xsd", COMMIT_ID_PATTERN, VALID_COMMIT_ID_PATTERN, "http://git.eclipse.org/c/xsd/org.eclipse.xsd.git/commit/?id=");
getDate();
return result;
}
/**
* Returns the build date as determined from the EMF project's branding plugin.
* @return the build date as determined from the EMF project's branding plugin.
*/
public String getDate()
{
Map<String, String> result = new LinkedHashMap<String, String>();
getIDs(result, "org.eclipse.emf", BUILD_ID_PATTERN, VALID_BUILD_ID_PATTERN, "");
if (!result.isEmpty())
{
String value = result.values().iterator().next();
try
{
Date date = new SimpleDateFormat("yyyyMMddHHmm").parse(value);
return new SimpleDateFormat("yyyy'-'MM'-'dd' at 'HH':'mm ").format(date);
}
catch (ParseException e)
{
// Ignore.
}
}
return null;
}
/**
* Populates the IDs with the information from {@code about.mappings} for the branding plugin with the give UI ID.
* @param ids the IDs to populate.
* @param iuID the ID of a branding plugin.
* @param idPattern the pattern for finding the ID in the {@code about.mappings}
* @param validIDPattern the pattern for validating and extracting the ID.
* @param prefix the prefix that will be prepended to the ID.
*/
private void getIDs(Map<String, String> ids, String iuID, Pattern idPattern, Pattern validIDPattern, String prefix)
{
IMetadataRepository metadataRepository = getCompositeMetadataRepository();
IArtifactRepository artifactRepository = getCompositeArtifactRepository();
IQueryResult<IInstallableUnit> ius = metadataRepository.query(QueryUtil.createIUQuery(iuID), new NullProgressMonitor());
for (Iterator<IInstallableUnit> i = ius.iterator(); i.hasNext();)
{
IInstallableUnit iu = i.next();
Collection<IArtifactKey> artifacts = iu.getArtifacts();
for (IArtifactKey artifactKey : artifacts)
{
IArtifactDescriptor[] artifactDescriptors = artifactRepository.getArtifactDescriptors(artifactKey);
for (IArtifactDescriptor artifactDescriptor : artifactDescriptors)
{
ByteArrayOutputStream artifact = new ByteArrayOutputStream();
artifactRepository.getArtifact(artifactDescriptor, artifact, new NullProgressMonitor());
ZipInputStream zipInputStream = null;
try
{
zipInputStream = new ZipInputStream(new ByteArrayInputStream(artifact.toByteArray()));
for (ZipEntry entry = zipInputStream.getNextEntry(); entry != null; entry = zipInputStream.getNextEntry())
{
String name = entry.getName();
if ("about.mappings".equals(name))
{
ByteArrayOutputStream content = new ByteArrayOutputStream();
byte[] bytes = new byte [1024];
for (int length = zipInputStream.read(bytes); length != -1; length = zipInputStream.read(bytes))
{
content.write(bytes, 0, length);
}
content.close();
String value = new String(content.toByteArray(), "UTF-8");
for (Matcher matcher = idPattern.matcher(value); matcher.find();)
{
String id = matcher.group(1);
Matcher idMatcher = validIDPattern.matcher(id);
if (idMatcher.matches())
{
ids.put(iuID.substring(iuID.lastIndexOf('.') + 1).toUpperCase(), prefix + idMatcher.group(1));
}
break;
}
break;
}
}
}
catch (IOException exception)
{
// Ignore.
}
finally
{
if (zipInputStream != null)
{
try
{
zipInputStream.close();
}
catch (IOException e)
{
// Ignore.
}
}
}
break;
}
}
break;
}
}
/**
* Returns the metadata repository of this analyzer.
* @return the metadata repository of this analyzer.
*/
private IMetadataRepository getMetadataRepository()
{
ICompositeRepository<?> compositeMetadataRepository = (ICompositeRepository<?>)getCompositeMetadataRepository();
IMetadataRepositoryManager metadataRepositoryManager = getMetadataRepositoryManager();
try
{
return metadataRepositoryManager.loadRepository(compositeMetadataRepository.getChildren().get(0), new NullProgressMonitor());
}
catch (Exception exception)
{
throw new IllegalStateException(exception);
}
}
@Override
public IStatus run(IProgressMonitor monitor) throws ProvisionException
{
return null;
}
}
}