/******************************************************************************* * Copyright (c) 2007, 2010 compeople AG 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: * compeople AG (Stefan Liebig) - initial API and implementation * IBM Corporation - ongoing development *******************************************************************************/ package org.eclipse.equinox.internal.p2.artifact.optimizers.jbdiff; import java.io.IOException; import java.io.OutputStream; import java.util.*; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.equinox.internal.p2.artifact.optimizers.VersionlessArtifactKey; import org.eclipse.equinox.internal.provisional.p2.artifact.repository.processing.ProcessingStep; import org.eclipse.equinox.internal.provisional.p2.artifact.repository.processing.ProcessingStepHandler; import org.eclipse.equinox.p2.core.ProvisionException; import org.eclipse.equinox.p2.metadata.IArtifactKey; import org.eclipse.equinox.p2.query.IQueryResult; import org.eclipse.equinox.p2.repository.artifact.*; import org.eclipse.equinox.p2.repository.artifact.spi.ArtifactDescriptor; import org.eclipse.equinox.p2.repository.artifact.spi.ProcessingStepDescriptor; public class Optimizer { private IArtifactRepository repository; private int width; private int depth; private boolean nosar; private static final String JBPATCH_STEP_ID = "org.eclipse.equinox.p2.repository.JBPatchStep"; //$NON-NLS-1$ private static final String JBPATCH_STEP_ZIP_ID = "org.eclipse.equinox.p2.repository.JBPatchZipStep"; //$NON-NLS-1$ private static final Comparator<IArtifactDescriptor> ARTIFACT_DESCRIPTOR_VERSION_COMPARATOR = new ArtifactDescriptorVersionComparator(); private static final Comparator<IArtifactKey> ARTIFACT_KEY_VERSION_COMPARATOR = new ArtifactKeyVersionComparator(); /** * This optimizer performs delta generation based on (currently) jbdiff. * The optimization can be controlled with the �width� and the �depth� parameter. * �width� defines for how many �related� artifact keys a delta should be generated, * starting from the most up-to-date. * �depth� defines to how many predecessor a delta should be generated. * * With AK(c-v) : AK - artifact key, c - artifact id, v - artifact version * the �repository content� can be viewed a two dimensional array, where the * artifact keys for the same component are in order of their version: * <pre><code> * w=1 w=2 * | | * | +------.------------+ d=2 * | | +----.---+ d=1 | * | | | | | v * [ v | | v v v * [ AK(x,2.0) AK(x,1.5) AK(x,1.1) ] * [ AK(y,2.0) AK(y,1.9) ] * [ AK(z,2.0) AK(z,1.5) AK(z,1.3) AK(z,1.0) ] * ] * </code></pre> * E.g: with a �width� of one and a �depth� of two the optimizer would * create two deltas for component �x� from 1.5 to 2.0 and from 1.1 to 2.0. * * @param repository * @param width * @param depth * @param nosar */ public Optimizer(IArtifactRepository repository, int width, int depth, boolean nosar) { this.repository = repository; this.width = width; this.depth = depth; this.nosar = nosar; } public void run() { System.out.println("Starting delta (jbdiff) optimizations (width=" + width + ", depth=" + depth + ", nosar=" + nosar + ")"); IQueryResult<IArtifactKey> queryResult = repository.query(ArtifactKeyQuery.ALL_KEYS, null); IArtifactKey[][] keys = getSortedRelatedArtifactKeys(queryResult); for (int i = 0; i < keys.length; i++) { if (keys[i].length < 2) // Nothing to diff here! continue; int minWidth = Math.min(width, keys[i].length); for (int j = 0; j < minWidth; j++) { IArtifactKey key = keys[i][j]; boolean isArchive = key.getClassifier().equals("plugin"); //$NON-NLS-1$ String proposedStrategy = isArchive && !nosar ? JBPATCH_STEP_ZIP_ID : JBPATCH_STEP_ID; optimize(keys[i], key, proposedStrategy); } } System.out.println("Done."); } private void optimize(IArtifactKey[] keys, IArtifactKey key, String proposedStrategy) throws OutOfMemoryError { boolean retry; do { retry = false; try { IArtifactDescriptor[] descriptors = repository.getArtifactDescriptors(key); IArtifactDescriptor complete = null; for (int k = 0; k < descriptors.length; k++) { IArtifactDescriptor descriptor = descriptors[k]; if (isCanonical(descriptor)) complete = descriptor; else if (isOptimized(descriptor, proposedStrategy)) { proposedStrategy = null; break; } } if (proposedStrategy != null && complete != null) optimize(complete, proposedStrategy, keys); } catch (OutOfMemoryError e) { if (JBPATCH_STEP_ID.equals(proposedStrategy)) throw e; proposedStrategy = JBPATCH_STEP_ID; System.out.println("Retry with " + proposedStrategy); retry = true; } } while (retry); } /** * This method retrieves a list of list of IArtifactKeys. The artifact keys in the * list of artifact keys are all �strongly related� to each other such that are * equal but not considering the versions. This list is sorted such that the * newer versions are first in the list.<p> * With AK(c-v) : AK - artifact key, c - artifact id, v - artifact version * the result is than, e.g. * <pre><code> * [ * [ AK(x,2.0) AK(x,1.5) AK(x,1.1) ] * [ AK(y,2.0) AK(y,1.9) ] * [ AK(z,2.0) AK(z,1.5) AK(z,1.3) AK(z,1.0) ] * ] * </code></pre> * @param artifactKeys * @return the sorted artifact keys */ private IArtifactKey[][] getSortedRelatedArtifactKeys(IQueryResult<IArtifactKey> artifactKeys) { Map<IArtifactKey, List<IArtifactKey>> map = new HashMap<IArtifactKey, List<IArtifactKey>>(); for (Iterator<IArtifactKey> iter = artifactKeys.iterator(); iter.hasNext();) { IArtifactKey nxt = iter.next(); IArtifactKey freeKey = new VersionlessArtifactKey(nxt); List<IArtifactKey> values = map.get(freeKey); if (values == null) { values = new ArrayList<IArtifactKey>(); map.put(freeKey, values); } values.add(nxt); } IArtifactKey[][] lists = new IArtifactKey[map.size()][]; int i = 0; for (List<IArtifactKey> artifactKeyList : map.values()) { IArtifactKey[] relatedArtifactKeys = artifactKeyList.toArray(new IArtifactKey[artifactKeyList.size()]); Arrays.sort(relatedArtifactKeys, ARTIFACT_KEY_VERSION_COMPARATOR); lists[i++] = relatedArtifactKeys; } int candidates = 0; for (int ii = 0; ii < lists.length; ii++) { for (int jj = 0; jj < lists[ii].length; jj++) { System.out.println(lists[ii][jj] + ", "); } System.out.println(""); if (lists[ii].length > 1) candidates++; } System.out.println("Candidates found: " + candidates); return lists; } private void optimize(IArtifactDescriptor complete, String strategy, IArtifactKey[] relatedArtifactKeys) { System.out.println("Optimizing " + complete); IArtifactDescriptor[] descriptors = getSortedCompletePredecessors(complete.getArtifactKey(), relatedArtifactKeys); int minDepth = Math.min(depth, descriptors.length); for (int i = 0; i < minDepth; i++) { System.out.println("\t with " + strategy + " against " + descriptors[i].getArtifactKey()); String predecessorData = descriptors[i].getArtifactKey().toExternalForm(); ArtifactDescriptor newDescriptor = new ArtifactDescriptor(complete); IProcessingStepDescriptor patchStep = new ProcessingStepDescriptor(strategy, predecessorData, true); IProcessingStepDescriptor[] steps = new IProcessingStepDescriptor[] {patchStep}; newDescriptor.setProcessingSteps(steps); newDescriptor.setProperty(IArtifactDescriptor.FORMAT, strategy); OutputStream repositoryStream = null; try { repositoryStream = repository.getOutputStream(newDescriptor); // Add in all the processing steps needed to optimize (e.g., pack200, ...) ProcessingStep diffStep = getProcessingStep(strategy); diffStep.initialize(repository.getProvisioningAgent(), patchStep, newDescriptor); ProcessingStepHandler handler = new ProcessingStepHandler(); OutputStream destination = handler.link(new ProcessingStep[] {diffStep}, repositoryStream, null); // Do the actual work by asking the repo to get the artifact and put it in the destination. IStatus status = repository.getArtifact(complete, destination, new NullProgressMonitor()); if (!status.isOK()) { System.out.println("Getting the artifact is not ok."); //$NON-NLS-1$ System.out.println(status); } } catch (ProvisionException e) { System.out.println("Skipping optimization of: " + descriptors[i].getArtifactKey()); //$NON-NLS-1$ System.out.println(e.getMessage()); e.printStackTrace(); } finally { if (repositoryStream != null) try { repositoryStream.close(); IStatus status = ProcessingStepHandler.checkStatus(repositoryStream); if (!status.isOK()) { System.out.println("Skipping optimization of: " + descriptors[i].getArtifactKey()); //$NON-NLS-1$ System.out.println(status.toString()); } } catch (IOException e) { System.out.println("Skipping optimization of: " + descriptors[i].getArtifactKey()); //$NON-NLS-1$ System.out.println(e.getMessage()); e.printStackTrace(); } } } } private ProcessingStep getProcessingStep(String strategy) { if (strategy.equals(JBPATCH_STEP_ID)) return new JBDiffStep(repository); return new JBDiffZipStep(repository); } private IArtifactDescriptor[] getSortedCompletePredecessors(IArtifactKey artifactKey, IArtifactKey[] relatedArtifactKeys) { // get all artifact keys List<IArtifactDescriptor> completeDescriptors = new ArrayList<IArtifactDescriptor>(relatedArtifactKeys.length); for (int i = 0; i < relatedArtifactKeys.length; i++) { // if we find �our self� skip if (relatedArtifactKeys[i].equals(artifactKey)) continue; // look for a complete artifact descriptor of the current key IArtifactDescriptor[] descriptors = repository.getArtifactDescriptors(relatedArtifactKeys[i]); for (int j = 0; j < descriptors.length; j++) { if (isCanonical(descriptors[j])) { completeDescriptors.add(descriptors[j]); break; } } } IArtifactDescriptor[] completeSortedDescriptors = completeDescriptors.toArray(new IArtifactDescriptor[completeDescriptors.size()]); // Sort, so to allow a depth lookup! Arrays.sort(completeSortedDescriptors, ARTIFACT_DESCRIPTOR_VERSION_COMPARATOR); return completeSortedDescriptors; } private boolean isOptimized(IArtifactDescriptor descriptor, String stepId) { if (descriptor.getProcessingSteps().length != 1) return false; return stepId.equals(descriptor.getProcessingSteps()[0].getProcessorId()); } private boolean isCanonical(IArtifactDescriptor descriptor) { // TODO length != 0 is not necessarily an indicator for not being complete! return descriptor.getProcessingSteps().length == 0; } static final class ArtifactDescriptorVersionComparator implements Comparator<IArtifactDescriptor> { public int compare(IArtifactDescriptor artifactDescriptor0, IArtifactDescriptor artifactDescriptor1) { return -1 * artifactDescriptor0.getArtifactKey().getVersion().compareTo(artifactDescriptor1.getArtifactKey().getVersion()); } } static final class ArtifactKeyVersionComparator implements Comparator<IArtifactKey> { public int compare(IArtifactKey artifactKey0, IArtifactKey artifactKey1) { return -1 * artifactKey0.getVersion().compareTo(artifactKey1.getVersion()); } } }