| /* |
| * Copyright (c) 2014, 2019 Eike Stepper (Loehne, Germany) 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 |
| * |
| * Contributors: |
| * Eike Stepper - initial API and implementation |
| */ |
| package org.eclipse.oomph.p2.internal.core; |
| |
| import org.eclipse.oomph.util.CollectionUtil; |
| import org.eclipse.oomph.util.IOUtil; |
| import org.eclipse.oomph.util.Pair; |
| import org.eclipse.oomph.util.PropertiesUtil; |
| import org.eclipse.oomph.util.StringUtil; |
| |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl; |
| import org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl.EObjectOutputStream; |
| import org.eclipse.emf.ecore.resource.impl.URIMappingRegistryImpl; |
| |
| import org.eclipse.equinox.app.IApplication; |
| import org.eclipse.equinox.app.IApplicationContext; |
| |
| import org.xml.sax.Attributes; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.parsers.SAXParser; |
| import javax.xml.parsers.SAXParserFactory; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedWriter; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileFilter; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.PrintStream; |
| import java.io.UnsupportedEncodingException; |
| import java.io.Writer; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Date; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Queue; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentLinkedDeque; |
| import java.util.concurrent.ConcurrentLinkedQueue; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| |
| /** |
| * @author Eike Stepper |
| */ |
| public final class P2Indexer implements IApplication |
| { |
| private static final String CHARSET = "UTF-8"; |
| |
| private final Map<URI, Repository> repositories = new ConcurrentHashMap<URI, Repository>(); |
| |
| /** |
| * The map from repository URL to the list of capabilities in that repository. |
| */ |
| private final Map<String, List<Capability>> capabilities = new HashMap<String, List<Capability>>(); |
| |
| /** |
| * The map from capability namespace to the set of capability names in that namespace. |
| */ |
| private final Map<String, Set<String>> capabilityIndex = new HashMap<String, Set<String>>(); |
| |
| private final SAXParserFactory parserFactory = SAXParserFactory.newInstance(); |
| |
| private final Queue<SAXParser> parserPool = new ConcurrentLinkedQueue<SAXParser>(); |
| |
| private final ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 4); |
| |
| private final Deque<Future<?>> deque = new ConcurrentLinkedDeque<Future<?>>(); |
| |
| private final long timeStamp = System.currentTimeMillis(); |
| |
| private int refreshHours = 24; |
| |
| private URI baseURI; |
| |
| private int maxRepos = Integer.MAX_VALUE; |
| |
| private boolean verbose; |
| |
| private Reporter reporter; |
| |
| public Object start(IApplicationContext context) throws Exception |
| { |
| long start = System.currentTimeMillis(); |
| String[] args = (String[])context.getArguments().get(IApplicationContext.APPLICATION_ARGS); |
| LinkedList<String> arguments = new LinkedList<String>(Arrays.asList(args)); |
| |
| try |
| { |
| File scanFolder = new File(arguments.removeFirst()).getCanonicalFile(); |
| refreshHours = Integer.parseInt(arguments.removeFirst()); |
| baseURI = URI.createURI(arguments.removeFirst()); |
| File outputFolder = new File(arguments.removeFirst()).getCanonicalFile(); |
| |
| while (!arguments.isEmpty()) |
| { |
| String arg = arguments.removeFirst(); |
| if ("-maxrepos".equals(arg) || "-m".equals(arg)) |
| { |
| maxRepos = Integer.parseInt(arguments.removeFirst()); |
| } |
| else if ("-verbose".equals(arg) || "-v".equals(arg)) |
| { |
| verbose = true; |
| } |
| else if ("-report".equals(arg) || "-r".equals(arg)) |
| { |
| File reportFolder = new File(arguments.removeFirst()); |
| reporter = new Reporter(baseURI.toString(), reportFolder); |
| } |
| } |
| |
| if (baseURI.hasTrailingPathSeparator()) |
| { |
| baseURI = baseURI.trimSegments(1); |
| } |
| |
| scanFolder(scanFolder, baseURI); |
| |
| for (Future<?> future = deque.pollFirst(); future != null; future = deque.pollFirst()) |
| { |
| future.get(); |
| } |
| |
| generateRepositoryMetadata(); |
| |
| for (Future<?> future = deque.pollFirst(); future != null; future = deque.pollFirst()) |
| { |
| future.get(); |
| } |
| |
| System.out.println(); |
| System.out.println("Generating index to " + outputFolder); |
| generateIndex(outputFolder); |
| |
| if (reporter != null) |
| { |
| System.out.println(); |
| System.out.println("Writing report to " + reporter.getReportFolder()); |
| reporter.writeReport(this); |
| } |
| } |
| finally |
| { |
| System.out.println(); |
| System.out.println("Took " + (System.currentTimeMillis() - start) / 1000 + " seconds."); |
| threadPool.shutdown(); |
| } |
| |
| return null; |
| } |
| |
| public void stop() |
| { |
| } |
| |
| private void scanFolder(final File folder, final URI uri) |
| { |
| if (repositories.size() >= maxRepos) |
| { |
| return; |
| } |
| |
| deque.addLast(threadPool.submit(new Runnable() |
| { |
| public void run() |
| { |
| File metadataFile = getMetadataFile(folder); |
| if (metadataFile != null) |
| { |
| if (verbose) |
| { |
| System.out.println("Found " + metadataFile); |
| } |
| |
| if (metadataFile.getName().startsWith("composite")) |
| { |
| repositories.put(uri, new Repository.Composite(P2Indexer.this, uri, metadataFile)); |
| } |
| else |
| { |
| repositories.put(uri, new Repository.Simple(P2Indexer.this, uri, metadataFile)); |
| } |
| } |
| } |
| })); |
| |
| File[] children = folder.listFiles(new FileFilter() |
| { |
| public boolean accept(File file) |
| { |
| return isValidFolder(file); |
| } |
| }); |
| |
| if (children != null) |
| { |
| Arrays.sort(children); |
| |
| for (final File child : children) |
| { |
| String name = child.getName(); |
| final String encodedName = URI.encodeSegment(name, false); |
| if (name.equals(URI.decode(encodedName))) |
| { |
| scheduleTask(new Runnable() |
| { |
| public void run() |
| { |
| scanFolder(child, uri.appendSegment(encodedName)); |
| } |
| }); |
| } |
| else |
| { |
| System.err.println("Can't encode " + child); |
| } |
| } |
| } |
| } |
| |
| private File getMetadataFile(File folder) |
| { |
| File p2IndexFile = new File(folder, "p2.index"); |
| if (p2IndexFile.canRead()) |
| { |
| Map<String, String> properties = PropertiesUtil.loadProperties(p2IndexFile); |
| String factoryOrder = properties.get("metadata.repository.factory.order"); |
| if (factoryOrder != null) |
| { |
| for (StringTokenizer tokenizer = new StringTokenizer(factoryOrder, ","); tokenizer.hasMoreTokens();) |
| { |
| String factory = tokenizer.nextToken(); |
| if ("!".equals(factory)) |
| { |
| break; |
| } |
| |
| if ("content.xml".equals(factory)) |
| { |
| File file = new File(folder, "content.jar"); |
| if (file.isFile()) |
| { |
| return file; |
| } |
| |
| file = new File(folder, "content.xml"); |
| if (file.isFile()) |
| { |
| return file; |
| } |
| } |
| |
| if ("compositeContent.xml".equals(factory)) |
| { |
| File file = new File(folder, "compositeContent.jar"); |
| if (file.isFile()) |
| { |
| return file; |
| } |
| |
| file = new File(folder, "compositeContent.xml"); |
| if (file.isFile()) |
| { |
| return file; |
| } |
| } |
| } |
| } |
| } |
| |
| File file = new File(folder, "content.jar"); |
| if (file.isFile()) |
| { |
| return file; |
| } |
| |
| file = new File(folder, "content.xml"); |
| if (file.isFile()) |
| { |
| return file; |
| } |
| |
| file = new File(folder, "compositeContent.jar"); |
| if (file.isFile()) |
| { |
| return file; |
| } |
| |
| file = new File(folder, "compositeContent.xml"); |
| if (file.isFile()) |
| { |
| return file; |
| } |
| |
| return null; |
| } |
| |
| private void generateRepositoryMetadata() |
| { |
| for (final Repository repository : repositories.values()) |
| { |
| scheduleTask(new Runnable() |
| { |
| public void run() |
| { |
| if (verbose) |
| { |
| System.out.println("Processing " + repository.getMetadataFile()); |
| } |
| |
| SAXParser parser = null; |
| |
| try |
| { |
| parser = acquireParser(); |
| repository.processsMetadata(parser); |
| } |
| catch (Exception ex) |
| { |
| String message = getStackTrace(ex); |
| if (reporter != null) |
| { |
| reporter.reportError(repository, message); |
| } |
| |
| System.err.println(repository.getURI() + " --> " + message); |
| } |
| finally |
| { |
| if (parser != null) |
| { |
| releaseParser(parser); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| private void scheduleTask(Runnable task) |
| { |
| deque.addLast(threadPool.submit(task)); |
| } |
| |
| private SAXParser acquireParser() throws ParserConfigurationException, SAXException |
| { |
| SAXParser parser = parserPool.poll(); |
| if (parser == null) |
| { |
| parser = parserFactory.newSAXParser(); |
| } |
| |
| return parser; |
| } |
| |
| private void releaseParser(SAXParser parser) |
| { |
| parserPool.add(parser); |
| } |
| |
| private void generateIndex(File outputFolder) throws Exception |
| { |
| int id = 0; |
| for (Repository repository : repositories.values()) |
| { |
| repository.setID(++id); |
| } |
| |
| outputFolder.mkdirs(); |
| int count = writeCapabilities(outputFolder); |
| writeRepositories(outputFolder); |
| writeCapabilityIndex(outputFolder); |
| |
| System.out.println(repositories.size() + " repositories"); |
| System.out.println(count + " capabilities"); |
| } |
| |
| private void writeCapabilityIndex(File outputFolder) throws FileNotFoundException, UnsupportedEncodingException, IOException |
| { |
| OutputStream outputStream = null; |
| |
| try |
| { |
| File capabilitiesFile = new File(outputFolder, "capabilities"); |
| outputStream = new FileOutputStream(capabilitiesFile); |
| |
| Map<Object, Object> options = new HashMap<Object, Object>(); |
| options.put(BinaryResourceImpl.OPTION_VERSION, BinaryResourceImpl.BinaryIO.Version.VERSION_1_1); |
| options.put(BinaryResourceImpl.OPTION_STYLE_DATA_CONVERTER, Boolean.TRUE); |
| options.put(BinaryResourceImpl.OPTION_BUFFER_CAPACITY, 8192); |
| |
| EObjectOutputStream stream = new BinaryResourceImpl.EObjectOutputStream(outputStream, options); |
| |
| stream.writeInt(refreshHours); |
| stream.writeCompressedInt(capabilityIndex.size()); |
| for (Map.Entry<String, Set<String>> entry : capabilityIndex.entrySet()) |
| { |
| Set<String> values = entry.getValue(); |
| stream.writeSegmentedString(entry.getKey()); |
| stream.writeCompressedInt(values.size()); |
| for (String value : values) |
| { |
| stream.writeSegmentedString(value); |
| } |
| } |
| |
| stream.flush(); |
| } |
| finally |
| { |
| IOUtil.close(outputStream); |
| } |
| } |
| |
| private void writeRepositories(File outputFolder) throws FileNotFoundException, UnsupportedEncodingException, IOException |
| { |
| OutputStream outputStream = null; |
| |
| try |
| { |
| File repositoriesFile = new File(outputFolder, "repositories"); |
| outputStream = new FileOutputStream(repositoriesFile); |
| |
| Map<Object, Object> options = new HashMap<Object, Object>(); |
| options.put(BinaryResourceImpl.OPTION_VERSION, BinaryResourceImpl.BinaryIO.Version.VERSION_1_1); |
| options.put(BinaryResourceImpl.OPTION_STYLE_DATA_CONVERTER, Boolean.TRUE); |
| options.put(BinaryResourceImpl.OPTION_BUFFER_CAPACITY, 8192); |
| |
| EObjectOutputStream stream = new BinaryResourceImpl.EObjectOutputStream(outputStream, options); |
| stream.writeLong(timeStamp); |
| stream.writeInt(refreshHours); |
| stream.writeInt(repositories.size()); |
| |
| List<Repository> problematicRepositories = new ArrayList<Repository>(); |
| for (Repository repository : repositories.values()) |
| { |
| repository.write(stream); |
| |
| if (repository.unresolvedChildren > 0) |
| { |
| problematicRepositories.add(repository); |
| } |
| } |
| |
| stream.writeInt(problematicRepositories.size()); |
| for (Repository repository : problematicRepositories) |
| { |
| stream.writeInt(repository.getID()); |
| stream.writeInt(repository.unresolvedChildren); |
| } |
| |
| stream.flush(); |
| } |
| finally |
| { |
| IOUtil.close(outputStream); |
| } |
| } |
| |
| private int writeCapabilities(final File outputFolder) throws InterruptedException |
| { |
| int count = capabilities.size(); |
| final CountDownLatch finished = new CountDownLatch(count); |
| |
| for (final Map.Entry<String, List<Capability>> entry : capabilities.entrySet()) |
| { |
| ++count; |
| threadPool.submit(new Runnable() |
| { |
| public void run() |
| { |
| try |
| { |
| writeCapability(outputFolder, entry.getKey(), entry.getValue()); |
| } |
| finally |
| { |
| finished.countDown(); |
| } |
| } |
| }); |
| } |
| |
| finished.await(); |
| return count; |
| } |
| |
| private void writeCapability(File outputFolder, String name, List<Capability> capabilities) |
| { |
| if (verbose) |
| { |
| System.out.println("Capability " + name); |
| } |
| |
| Map<Repository, Set<String>> versions = new HashMap<Repository, Set<String>>(); |
| for (Capability capability : capabilities) |
| { |
| Repository repository = capability.getRepository(); |
| if (repositories.containsKey(repository.getURI())) |
| { |
| Set<String> set = versions.get(repository); |
| if (set == null) |
| { |
| set = new HashSet<String>(); |
| versions.put(repository, set); |
| } |
| |
| set.add(capability.getVersion()); |
| } |
| } |
| |
| List<String> lines = new ArrayList<String>(); |
| lines.add(Long.toString(timeStamp)); |
| |
| for (Map.Entry<Repository, Set<String>> versionEntry : versions.entrySet()) |
| { |
| Repository repository = versionEntry.getKey(); |
| StringBuilder builder = new StringBuilder(); |
| builder.append(repository.getID()); |
| |
| for (String version : versionEntry.getValue()) |
| { |
| builder.append(","); |
| builder.append(version); |
| } |
| |
| lines.add(builder.toString()); |
| } |
| |
| try |
| { |
| File file = new File(outputFolder, name); |
| file.getParentFile().mkdirs(); |
| IOUtil.writeLines(file, CHARSET, lines); |
| } |
| catch (Exception ex) |
| { |
| System.err.println("Capability " + name + " --> " + getStackTrace(ex)); |
| } |
| } |
| |
| /** |
| * TODO Use {@link IOUtil#isValidFolder(File)}. |
| */ |
| private static boolean isValidFolder(File folder) |
| { |
| try |
| { |
| return folder.isDirectory() && folder.getAbsoluteFile().equals(folder.getCanonicalFile()); |
| } |
| catch (IOException ex) |
| { |
| return false; |
| } |
| } |
| |
| private static String getStackTrace(Exception exception) |
| { |
| try |
| { |
| ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
| PrintStream out = new PrintStream(bytes); |
| exception.printStackTrace(out); |
| return bytes.toString(); |
| } |
| catch (Exception ex) |
| { |
| return ex.getMessage(); |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private static abstract class Repository extends DefaultHandler |
| { |
| private static final String XML_SUFFIX = ".xml"; |
| |
| private static final String JAR_SUFFIX = ".jar"; |
| |
| private static final long NO_TIMESTAMP = 0; |
| |
| protected final List<Composite> composites = new ArrayList<Composite>(); |
| |
| protected final P2Indexer indexer; |
| |
| protected final URI uri; |
| |
| protected final File metadataFile; |
| |
| protected String elementPath; |
| |
| protected int id; |
| |
| protected long timestamp = NO_TIMESTAMP; |
| |
| protected int unresolvedChildren; |
| |
| public Repository(P2Indexer indexer, URI uri, File metadataFile) |
| { |
| this.indexer = indexer; |
| this.uri = uri; |
| this.metadataFile = metadataFile; |
| |
| if (indexer.reporter != null) |
| { |
| indexer.reporter.reportRepository(this); |
| } |
| } |
| |
| public File getMetadataFile() |
| { |
| return metadataFile; |
| } |
| |
| public URI getURI() |
| { |
| return uri; |
| } |
| |
| public int getID() |
| { |
| return id; |
| } |
| |
| public void setID(int id) |
| { |
| this.id = id; |
| } |
| |
| public long getTimestamp() |
| { |
| return timestamp; |
| } |
| |
| public List<Composite> getComposites() |
| { |
| return composites; |
| } |
| |
| public abstract boolean isComposed(); |
| |
| public boolean isCompressed() |
| { |
| return metadataFile.getName().endsWith(JAR_SUFFIX); |
| } |
| |
| public int getCapabilityCount() |
| { |
| return 0; |
| } |
| |
| public void processsMetadata(SAXParser parser) throws IOException, SAXException |
| { |
| JarFile jarFile = null; |
| InputStream inputStream = null; |
| |
| try |
| { |
| if (isCompressed()) |
| { |
| String name = metadataFile.getName(); |
| name = name.substring(0, name.length() - JAR_SUFFIX.length()) + XML_SUFFIX; |
| |
| jarFile = new JarFile(metadataFile); |
| JarEntry jarEntry = jarFile.getJarEntry(name); |
| inputStream = jarFile.getInputStream(jarEntry); |
| } |
| else |
| { |
| inputStream = new FileInputStream(metadataFile); |
| } |
| |
| inputStream = new BufferedInputStream(inputStream); |
| parser.parse(inputStream, this); |
| } |
| finally |
| { |
| IOUtil.close(inputStream); |
| |
| if (jarFile != null) |
| { |
| jarFile.close(); |
| } |
| } |
| } |
| |
| protected boolean startElement(String elementPath, Attributes attributes) |
| { |
| if ("repository>properties>property".equals(elementPath)) |
| { |
| if (timestamp == NO_TIMESTAMP) |
| { |
| String name = attributes.getValue("name"); |
| if ("p2.timestamp".equals(name)) |
| { |
| String value = attributes.getValue("value"); |
| if (value != null) |
| { |
| try |
| { |
| timestamp = Long.parseLong(value); |
| } |
| catch (NumberFormatException ex) |
| { |
| String message = "Bad timestamp value '" + value + "'"; |
| if (indexer.reporter != null) |
| { |
| message = indexer.reporter.reportError(this, message); |
| } |
| |
| System.err.println(uri + " --> " + message); |
| } |
| } |
| else |
| { |
| String message = "No timestamp value"; |
| if (indexer.reporter != null) |
| { |
| message = indexer.reporter.reportError(this, message); |
| } |
| |
| System.err.println(uri + " --> " + message); |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public final void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException |
| { |
| if (elementPath == null) |
| { |
| elementPath = qName; |
| } |
| else |
| { |
| elementPath += ">" + qName; |
| } |
| |
| startElement(elementPath, attributes); |
| } |
| |
| @Override |
| public final void endElement(String uri, String localName, String qName) throws SAXException |
| { |
| int pos = elementPath.lastIndexOf('>'); |
| if (pos >= 0) |
| { |
| elementPath = elementPath.substring(0, pos); |
| } |
| else |
| { |
| elementPath = null; |
| } |
| } |
| |
| public void write(EObjectOutputStream stream) throws IOException |
| { |
| stream.writeURI(uri); |
| stream.writeBoolean(isComposed()); |
| stream.writeBoolean(isCompressed()); |
| stream.writeLong(timestamp); |
| |
| if (!isComposed()) |
| { |
| stream.writeInt(getCapabilityCount()); |
| } |
| |
| for (Composite composite : composites) |
| { |
| stream.writeBoolean(true); |
| stream.writeInt(composite.getID()); |
| } |
| |
| stream.writeBoolean(false); |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + (uri == null ? 0 : uri.hashCode()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) |
| { |
| if (this == obj) |
| { |
| return true; |
| } |
| |
| if (obj == null) |
| { |
| return false; |
| } |
| |
| if (getClass() != obj.getClass()) |
| { |
| return false; |
| } |
| |
| Repository other = (Repository)obj; |
| if (uri == null) |
| { |
| if (other.uri != null) |
| { |
| return false; |
| } |
| } |
| else if (!uri.equals(other.uri)) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return uri.toString(); |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private static final class Simple extends Repository |
| { |
| private int capabilityCount; |
| |
| public Simple(P2Indexer indexer, URI uri, File metadataFile) |
| { |
| super(indexer, uri, metadataFile); |
| } |
| |
| @Override |
| public boolean isComposed() |
| { |
| return false; |
| } |
| |
| @Override |
| public int getCapabilityCount() |
| { |
| return capabilityCount; |
| } |
| |
| @Override |
| protected boolean startElement(String elementPath, Attributes attributes) |
| { |
| if (super.startElement(elementPath, attributes)) |
| { |
| return true; |
| } |
| |
| if ("repository>units>unit>provides>provided".equals(elementPath)) |
| { |
| String namespace = URI.encodeSegment(attributes.getValue("namespace"), false); |
| String name = URI.encodeSegment(attributes.getValue("name"), false); |
| String version = attributes.getValue("version"); |
| |
| String qualifiedName = namespace + "/" + name; |
| if (name.equals(".") || name.equals("..") || name.startsWith("file:")) |
| { |
| if (indexer.verbose) |
| { |
| System.err.println("Skipping " + qualifiedName); |
| } |
| |
| return true; |
| } |
| |
| synchronized (indexer.capabilityIndex) |
| { |
| CollectionUtil.add(indexer.capabilityIndex, namespace, name); |
| |
| List<Capability> list = indexer.capabilities.get(qualifiedName); |
| if (list == null) |
| { |
| list = new ArrayList<Capability>(); |
| indexer.capabilities.put(qualifiedName, list); |
| } |
| |
| list.add(new Capability(this, version)); |
| ++capabilityCount; |
| } |
| |
| return true; |
| } |
| |
| return false; |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private static final class Composite extends Repository |
| { |
| public Composite(P2Indexer indexer, URI uri, File metadataFile) |
| { |
| super(indexer, uri, metadataFile); |
| } |
| |
| @Override |
| public boolean isComposed() |
| { |
| return true; |
| } |
| |
| @Override |
| protected boolean startElement(String elementPath, Attributes attributes) |
| { |
| if (super.startElement(elementPath, attributes)) |
| { |
| return true; |
| } |
| |
| if ("repository>children>child".equals(elementPath)) |
| { |
| String child = attributes.getValue("location"); |
| if (!StringUtil.isEmpty(child)) |
| { |
| URI childURI = URI.createURI(child); |
| if (childURI.hasTrailingPathSeparator()) |
| { |
| childURI = childURI.trimSegments(1); |
| } |
| |
| if (childURI.isRelative()) |
| { |
| childURI = childURI.resolve(uri.hasTrailingPathSeparator() ? uri : uri.appendSegment("")); |
| } |
| else if (!indexer.baseURI.scheme().equals(childURI.scheme()) && indexer.baseURI.authority().equals(childURI.authority())) |
| { |
| childURI = indexer.baseURI.appendSegments(childURI.segments()); |
| } |
| |
| Repository childRepository = indexer.repositories.get(childURI); |
| if (childRepository != null) |
| { |
| childRepository.composites.add(this); |
| } |
| else if (indexer.baseURI.scheme().equals(childURI.scheme()) && indexer.baseURI.authority().equals(childURI.authority())) |
| { |
| ++unresolvedChildren; |
| |
| String message = "Child repository " + childURI + " not found"; |
| if (indexer.reporter != null) |
| { |
| message = indexer.reporter.reportError(this, message); |
| } |
| |
| System.err.println(uri + " --> " + message); |
| } |
| } |
| } |
| |
| return false; |
| } |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private static final class Capability |
| { |
| private final Repository repository; |
| |
| private final String version; |
| |
| public Capability(Repository repository, String version) |
| { |
| this.repository = repository; |
| this.version = version; |
| } |
| |
| public Repository getRepository() |
| { |
| return repository; |
| } |
| |
| public String getVersion() |
| { |
| return version; |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private static final class Reporter |
| { |
| private final File reportFolder; |
| |
| private final ProjectRegistry projectRegistry; |
| |
| private final ProjectMapper projectMapper; |
| |
| private final Map<String, Project> projectsByID = new HashMap<String, Project>(); |
| |
| private final Map<Repository, Project> projectsByRepository = new HashMap<Repository, Project>(); |
| |
| public Reporter(String baseURI, File reportFolder) |
| { |
| this.reportFolder = reportFolder; |
| projectRegistry = new ProjectRegistry(); |
| projectMapper = new ProjectMapper(baseURI.toString()); |
| } |
| |
| public File getReportFolder() |
| { |
| return reportFolder; |
| } |
| |
| public synchronized void reportRepository(Repository repository) |
| { |
| String projectID = projectMapper.getProjectID(repository.getURI()); |
| if (projectID != null) |
| { |
| Project project = projectsByID.get(projectID); |
| if (project == null) |
| { |
| String projectName = projectRegistry.getProjectName(projectID); |
| project = new Project(projectID, projectName); |
| projectsByID.put(projectID, project); |
| } |
| |
| projectsByRepository.put(repository, project); |
| project.addRepository(repository); |
| } |
| } |
| |
| public synchronized String reportError(Repository repository, String message) |
| { |
| Project project = projectsByRepository.get(repository); |
| if (project != null) |
| { |
| project.addError(repository, message); |
| message += " (" + project.getID() + ")"; |
| } |
| |
| return message; |
| } |
| |
| public void writeReport(P2Indexer indexer) throws IOException |
| { |
| Map<Repository, Set<Repository>> childrenMap = new HashMap<Repository, Set<Repository>>(); |
| Map<Repository, Pair<Project, Integer>> ids = new HashMap<Repository, Pair<Project, Integer>>(); |
| int nextID = 0; |
| |
| for (Repository repository : indexer.repositories.values()) |
| { |
| Project project = projectsByRepository.get(repository); |
| ids.put(repository, Pair.create(project, ++nextID)); |
| |
| for (Repository.Composite composite : repository.getComposites()) |
| { |
| CollectionUtil.add(childrenMap, composite, repository); |
| } |
| } |
| |
| List<Project> projects = new ArrayList<Project>(projectsByID.values()); |
| Collections.sort(projects, new Comparator<Project>() |
| { |
| public int compare(Project o1, Project o2) |
| { |
| return o1.getLabel().toLowerCase().compareTo(o2.getLabel().toLowerCase()); |
| } |
| }); |
| |
| int projectRepos = 0; |
| int erroneousProjects = 0; |
| int erroneousRepos = 0; |
| for (Project project : projects) |
| { |
| projectRepos += project.getTotalRepos(); |
| |
| int erroneousProjectRepos = project.getErroneousRepos(); |
| erroneousRepos += erroneousProjectRepos; |
| |
| if (erroneousProjectRepos > 0) |
| { |
| ++erroneousProjects; |
| } |
| } |
| |
| Writer writer = null; |
| |
| try |
| { |
| reportFolder.mkdirs(); |
| |
| writer = new BufferedWriter(new FileWriter(new File(reportFolder, "index.html"))); |
| writer.write("<html>\n"); |
| writer.write("<head>\n"); |
| writer.write("</head>\n"); |
| writer.write("<body>\n"); |
| writer.write("<h1>Project Repository Reports</h1>\n"); |
| writer.write("Projects with repositories: " + projects.size() |
| + " <a href=\"mailto:stepper@esc-net.de?cc=ed.merks@gmail.com&subject=My%20project%20is%20missing\">Report missing project</a><br>\n"); |
| if (erroneousProjects > 0) |
| { |
| writer.write("<font color=\"#ff0000\"><b>Projects with erroneous repositories: " + erroneousProjects + "</b></font><br>\n"); |
| } |
| |
| writer.write("Total repositories: " + projectRepos + "<br>\n"); |
| if (erroneousRepos > 0) |
| { |
| writer.write("<font color=\"#ff0000\"><b>Erroneous repositories: " + erroneousRepos + "</b></font><br>\n"); |
| } |
| |
| writer.write("<hr>\n"); |
| writer.write("<ul>\n"); |
| } |
| catch (Exception ex) |
| { |
| ex.printStackTrace(); |
| } |
| |
| for (Project project : projects) |
| { |
| if (project.writeReport(reportFolder, childrenMap, ids) && writer != null) |
| { |
| int errors = project.getErroneousRepos(); |
| String suffix = errors > 0 |
| ? "<font color=\"#ff0000\"><b> (" + errors + " erroneous " + (errors == 1 ? "repository" : "repositories") + ")</b></font>" |
| : ""; |
| |
| writer.write("<li><a href=\"" + project.getID() + ".html\">" + project.getLabel() + "</a>" + suffix + "\n"); |
| } |
| } |
| |
| if (writer != null) |
| { |
| writer.write("</ul>\n"); |
| writer.write("</body>\n"); |
| writer.write("</html>\n"); |
| IOUtil.close(writer); |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private static final class ProjectRegistry |
| { |
| private static final String PROJECTS_TXT = System.getProperty("projects.txt", |
| "/home/data/httpd/download.eclipse.org/oomph/archive/projects/projects.txt"); |
| |
| private final Map<String, String> names = new HashMap<String, String>(); |
| |
| public ProjectRegistry() |
| { |
| File file = new File(PROJECTS_TXT); |
| if (file.exists()) |
| { |
| for (String line : IOUtil.readLines(file, "UTF-8")) |
| { |
| int tab = line.indexOf('\t'); |
| String id = line.substring(0, tab); |
| String name = line.substring(tab + 1); |
| |
| names.put(id, name); |
| } |
| } |
| else |
| { |
| System.out.println(ProjectRegistry.class.getSimpleName() + ": " + PROJECTS_TXT + " not found."); |
| } |
| } |
| |
| public String getProjectName(String id) |
| { |
| return names.get(id); |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private static final class ProjectMapper |
| { |
| private static final String MAPPINGS_TXT = System.getProperty("mappings.txt", |
| "/home/data/httpd/download.eclipse.org/oomph/archive/projects/mappings.txt"); |
| |
| private final URIMappingRegistryImpl registry = new URIMappingRegistryImpl(); |
| |
| private final String baseURI; |
| |
| public ProjectMapper(String baseURI) |
| { |
| this.baseURI = baseURI; |
| |
| File file = new File(MAPPINGS_TXT); |
| if (file.exists()) |
| { |
| for (String line : IOUtil.readLines(file, "UTF-8")) |
| { |
| int tab = line.indexOf('\t'); |
| String path = line.substring(0, tab); |
| String project = line.substring(tab + 1); |
| |
| registry.put(createURI(path), URI.createURI("project://" + project + "/")); |
| } |
| } |
| else |
| { |
| System.out.println(ProjectMapper.class.getSimpleName() + ": " + MAPPINGS_TXT + " not found."); |
| } |
| } |
| |
| public String getProjectID(URI uri) |
| { |
| URI projectURI = registry.getURI(uri); |
| if (projectURI != null && "project".equals(projectURI.scheme())) |
| { |
| return projectURI.authority(); |
| } |
| |
| return null; |
| } |
| |
| private URI createURI(String path) |
| { |
| if (!path.endsWith("/")) |
| { |
| path += "/"; |
| } |
| |
| return URI.createURI(baseURI + "/" + path); |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private static final class Project |
| { |
| private final String id; |
| |
| private final String name; |
| |
| private final Map<Repository, List<String>> repositories = Collections.synchronizedMap(new HashMap<Repository, List<String>>()); |
| |
| public Project(String id, String name) |
| { |
| this.id = id; |
| this.name = name; |
| } |
| |
| public String getID() |
| { |
| return id; |
| } |
| |
| public String getLabel() |
| { |
| return name != null && name.length() != 0 ? name : id; |
| } |
| |
| public int getTotalRepos() |
| { |
| return repositories.size(); |
| } |
| |
| public int getComposedRepos() |
| { |
| int result = 0; |
| for (Repository repository : repositories.keySet()) |
| { |
| if (repository.isComposed()) |
| { |
| ++result; |
| } |
| } |
| |
| return result; |
| } |
| |
| public int getErroneousRepos() |
| { |
| int result = 0; |
| for (List<String> errors : repositories.values()) |
| { |
| if (!errors.isEmpty()) |
| { |
| ++result; |
| } |
| } |
| |
| return result; |
| } |
| |
| public int getTotalErrors() |
| { |
| int result = 0; |
| for (List<String> errors : repositories.values()) |
| { |
| result += errors.size(); |
| } |
| |
| return result; |
| } |
| |
| public void addRepository(Repository repository) |
| { |
| repositories.put(repository, new ArrayList<String>()); |
| } |
| |
| public void addError(Repository repository, String message) |
| { |
| List<String> errors = repositories.get(repository); |
| errors.add(message); |
| } |
| |
| public boolean writeReport(File folder, Map<Repository, Set<Repository>> childrenMap, Map<Repository, Pair<Project, Integer>> ids) |
| { |
| if (repositories.isEmpty()) |
| { |
| return false; |
| } |
| |
| BufferedWriter writer = null; |
| |
| try |
| { |
| List<Map.Entry<Repository, List<String>>> entries = new ArrayList<Map.Entry<Repository, List<String>>>(repositories.entrySet()); |
| Collections.sort(entries, new Comparator<Map.Entry<Repository, List<String>>>() |
| { |
| public int compare(Map.Entry<Repository, List<String>> o1, Map.Entry<Repository, List<String>> o2) |
| { |
| return o1.getKey().getURI().toString().compareTo(o2.getKey().getURI().toString()); |
| } |
| }); |
| |
| int totalRepos = getTotalRepos(); |
| int compositeRepos = getComposedRepos(); |
| int simpleRepos = totalRepos - compositeRepos; |
| int erroneousRepos = getErroneousRepos(); |
| int totalErrors = getTotalErrors(); |
| |
| folder.mkdirs(); |
| |
| writer = new BufferedWriter(new FileWriter(new File(folder, getFileName()))); |
| writer.write("<html>\n"); |
| writer.write("<head>\n"); |
| writer.write("</head>\n"); |
| writer.write("<body>\n"); |
| writer.write("<h1><a href=\"https://projects.eclipse.org/projects/" + id + "\">" + (name != null && name.length() != 0 ? name : id) + "</a></h1>\n"); |
| writer.write("<hr>\n"); |
| |
| writer.write("<table border=\"0\">\n"); |
| writer.write("<tr><td>Simple repositories:</td><td align=\"right\">" + simpleRepos + "</td><td></td></tr>\n"); |
| writer.write("<tr><td>Composite repositories:</td><td align=\"right\">" + compositeRepos + "</td><td></td></tr>\n"); |
| writer.write("<tr><td>Total repositories:</td><td align=\"right\">" + (simpleRepos + compositeRepos) + "</td><td></td></tr>\n"); |
| |
| if (totalErrors > 0) |
| { |
| writer.write("<tr><td><font color=\"#ff0000\"><b>Erroneous repositories:</b></font></td><td align=\"right\"><font color=\"#ff0000\"><b>" |
| + erroneousRepos + "</b></font></td><td>" + (erroneousRepos > 0 ? " <a href=\"#err0\">First error</a>" : "") + "</td></tr>\n"); |
| writer.write("<tr><td><font color=\"#ff0000\"><b>Total errors:</b></font></td><td align=\"right\"><font color=\"#ff0000\"><b>" + totalErrors |
| + "</b></font></td><td></td></tr>\n"); |
| } |
| |
| writer.write("</table>\n"); |
| |
| int erroneousRepo = 0; |
| for (Map.Entry<Repository, List<String>> entry : entries) |
| { |
| Repository repository = entry.getKey(); |
| List<String> errors = entry.getValue(); |
| |
| Pair<Project, Integer> pair = ids.get(repository); |
| Integer idWrapper = pair.getElement2(); |
| int id = idWrapper; |
| |
| writer.write("<h2><a name=\"repo" + id + "\">" + (errors.isEmpty() ? "" : "<a name=\"err" + erroneousRepo + "\">") |
| + (repository.isComposed() ? "Composite" : "Simple") + " Repository <a href=\"" + repository.getURI() + "\">" + repository.getURI() |
| + "</a></h2>\n"); |
| |
| writer.write("<ul>"); |
| |
| writer.write("<li><span style=\"white-space: nowrap;\">"); |
| writer.write(new Date(repository.getTimestamp()).toString()); |
| writer.write("</span> ("); |
| writer.write(String.valueOf(repository.getTimestamp())); |
| writer.write(")"); |
| |
| writer.write("<li>"); |
| int capabilityCount = getCapabilityCount(repository, childrenMap); |
| writer.write(String.valueOf(capabilityCount)); |
| writer.write(capabilityCount == 1 ? " capability" : " capabilities"); |
| |
| if (!errors.isEmpty()) |
| { |
| writer.write("<li><font color=\"#ff0000\"><b>"); |
| writer.write(String.valueOf(errors.size())); |
| writer.write(" "); |
| writer.write(errors.size() == 1 ? "error" : "errors"); |
| writer.write("</b></font>"); |
| } |
| |
| if (repository.isCompressed()) |
| { |
| writer.write("<li>Compressed"); |
| } |
| |
| writer.write("</ul>"); |
| writer.write("<div style=\"margin-left: 24px;\">"); |
| |
| if (!errors.isEmpty()) |
| { |
| writer.write("<h3>Errors</h3>\n"); |
| writer.write("<ul>\n"); |
| |
| for (String error : errors) |
| { |
| writer.write("<li><font color=\"#ff0000\"><b>" + error.replace("\n", "<br>") + "</b></font>\n"); |
| } |
| |
| if (++erroneousRepo < erroneousRepos) |
| { |
| writer.write("<li><a href=\"#err" + erroneousRepo + "\">Next error</a>\n"); |
| } |
| |
| writer.write("</ul>\n"); |
| writer.write("</div>"); |
| } |
| |
| Set<Repository> children = childrenMap.get(repository); |
| if (children != null && !children.isEmpty()) |
| { |
| writer.write("<h3>Children</h3>\n"); |
| writer.write("<ul>\n"); |
| |
| for (Repository child : children) |
| { |
| writer.write("<li>"); |
| writeRepositoryLink(writer, child, ids); |
| writer.write("\n"); |
| } |
| |
| writer.write("</ul>\n"); |
| } |
| |
| List<Repository.Composite> composites = repository.getComposites(); |
| if (composites != null && !composites.isEmpty()) |
| { |
| writer.write("<h3>Composites</h3>\n"); |
| writer.write("<ul>\n"); |
| |
| for (Repository.Composite composite : composites) |
| { |
| writer.write("<li>"); |
| writeRepositoryLink(writer, composite, ids); |
| writer.write("\n"); |
| } |
| |
| writer.write("</ul>\n"); |
| } |
| |
| writer.write("</div>"); |
| } |
| |
| writer.write("</body>\n"); |
| writer.write("</html>\n"); |
| } |
| catch (Exception ex) |
| { |
| ex.printStackTrace(); |
| } |
| finally |
| { |
| IOUtil.close(writer); |
| } |
| |
| return true; |
| } |
| |
| private void writeRepositoryLink(BufferedWriter writer, Repository repository, Map<Repository, Pair<Project, Integer>> ids) throws IOException |
| { |
| Pair<Project, Integer> pair = ids.get(repository); |
| Project project = pair.getElement1(); |
| int id = pair.getElement2(); |
| |
| String href = project != null ? project.getFileName() + "#repo" + id : repository.getURI().toString(); |
| writer.write("<a href=\"" + href + "\">" + repository.getURI() + "</a>"); |
| } |
| |
| private String getFileName() |
| { |
| return id + ".html"; |
| } |
| |
| private int getCapabilityCount(Repository repository, Map<Repository, Set<Repository>> childrenMap) |
| { |
| if (repository instanceof Repository.Composite) |
| { |
| int sum = 0; |
| |
| Set<Repository> children = childrenMap.get(repository); |
| if (children != null && !children.isEmpty()) |
| { |
| for (Repository child : children) |
| { |
| sum += getCapabilityCount(child, childrenMap); |
| } |
| } |
| |
| return sum; |
| } |
| |
| return repository.getCapabilityCount(); |
| } |
| } |
| } |
| } |