blob: 35d03d88c7ae65b6fb4344054099c6db720f2908 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* James Blackburn (Broadcom Corp.) - ongoing development
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427
* Mickael Istria (Red Hat Inc.) - Bug 488937
*******************************************************************************/
package org.eclipse.core.internal.watson;
import java.io.*;
import java.util.*;
import org.eclipse.core.internal.dtree.*;
import org.eclipse.core.runtime.*;
/** <code>ElementTreeWriter</code> flattens an ElementTree
* onto a data output stream.
*
* <p>This writer generates the most up-to-date format
* of a saved element tree (cf. readers, which must usually also
* deal with backward compatibility issues). The flattened
* representation always includes a format version number.
*
* <p>The writer has an <code>IElementInfoFactory</code>,
* which it consults for writing element infos.
*
* <p>Element tree writers are thread-safe; several
* threads may share a single writer.
*
*/
public class ElementTreeWriter {
/**
* The current format version number.
*/
public static final int CURRENT_FORMAT = 1;
/**
* Constant representing infinite depth
*/
public static final int D_INFINITE = DataTreeWriter.D_INFINITE;
/**
* For writing DeltaDataTrees
*/
protected DataTreeWriter dataTreeWriter;
/**
* Constructs a new element tree writer that works for
* the given element info flattener.
*/
public ElementTreeWriter(final IElementInfoFlattener flattener) {
/* wrap the IElementInfoFlattener in an IDataFlattener */
IDataFlattener f = new IDataFlattener() {
@Override
public void writeData(IPath path, Object data, DataOutput output) throws IOException {
// never write the root node of an ElementTree
//because it contains the parent backpointer.
if (!Path.ROOT.equals(path)) {
flattener.writeElement(path, data, output);
}
}
@Override
public Object readData(IPath path, DataInput input) {
return null;
}
};
dataTreeWriter = new DataTreeWriter(f);
}
/**
* Sorts the given array of trees so that the following rules are true:
* - The first tree has no parent
* - No tree has an ancestor with a greater index in the array.
* If there are no missing parents in the given trees array, this means
* that in the resulting array, the i'th tree's parent will be tree i-1.
* The input tree array may contain duplicate trees.
* The sort order is written to the given output stream.
*/
protected ElementTree[] sortTrees(ElementTree[] trees, DataOutput output) throws IOException {
/* the sorted list */
int numTrees = trees.length;
ElementTree[] sorted = new ElementTree[numTrees];
int[] order = new int[numTrees];
/* first build a table of ElementTree -> HashMap of Integers(indices in trees array) */
HashMap<ElementTree, List<Integer>> table = new HashMap<>(numTrees * 2 + 1);
for (int i = 0; i < trees.length; i++) {
List<Integer> indices = table.get(trees[i]);
if (indices == null) {
indices = new ArrayList<>();
table.put(trees[i], indices);
}
indices.add(i);
}
/* find the oldest tree (a descendent of all other trees) */
ElementTree oldest = trees[ElementTree.findOldest(trees)];
/**
* Walk through the chain of trees from oldest to newest,
* adding them to the sorted list as we go.
*/
int i = numTrees - 1;
while (i >= 0) {
/* add all instances of the current oldest tree to the sorted list */
List<Integer> indices = table.remove(oldest);
for (Enumeration<Integer> e = Collections.enumeration(indices); e.hasMoreElements();) {
Integer next = e.nextElement();
sorted[i] = oldest;
order[i] = next.intValue();
i--;
}
if (i >= 0) {
/* find the next tree in the list */
ElementTree parent = oldest.getParent();
while (table.get(parent) == null) {
if (parent == null) {
throw new IOException("null parent found while sorting trees"); //$NON-NLS-1$
}
parent = parent.getParent();
}
oldest = parent;
}
}
/* write the order array */
for (i = 0; i < numTrees; i++) {
writeNumber(order[i], output);
}
return sorted;
}
/**
* Writes the delta describing the changes that have to be made
* to newerTree to obtain olderTree.
*
* @param path The path of the subtree to write. All nodes on the path above
* the subtree are represented as empty nodes.
* @param depth The depth of the subtree to write. A depth of zero writes a
* single node, and a depth of D_INFINITE writes the whole subtree.
* @param output The stream to write the subtree to.
*/
public void writeDelta(ElementTree olderTree, ElementTree newerTree, IPath path, int depth, final DataOutput output, IElementComparator comparator) throws IOException {
/* write the version number */
writeNumber(CURRENT_FORMAT, output);
/**
* Note that in current ElementTree usage, the newest
* tree is the complete tree, and older trees are just
* deltas on the new tree.
*/
DeltaDataTree completeTree = newerTree.getDataTree();
DeltaDataTree derivedTree = olderTree.getDataTree();
DeltaDataTree deltaToWrite = null;
deltaToWrite = completeTree.forwardDeltaWith(derivedTree, comparator);
Assert.isTrue(deltaToWrite.isImmutable());
dataTreeWriter.writeTree(deltaToWrite, path, depth, output);
}
/**
* Writes an array of ElementTrees to the given output stream.
* @param trees A chain of ElementTrees, where on tree in the list is
* complete, and all other trees are deltas on the previous tree in the list.
* @param path The path of the subtree to write. All nodes on the path above
* the subtree are represented as empty nodes.
* @param depth The depth of the subtree to write. A depth of zero writes a
* single node, and a depth of D_INFINITE writes the whole subtree.
* @param output The stream to write the subtree to.
*/
public void writeDeltaChain(ElementTree[] trees, IPath path, int depth, DataOutput output, IElementComparator comparator) throws IOException {
/* Write the format version number */
writeNumber(CURRENT_FORMAT, output);
/* Write the number of trees */
int treeCount = trees.length;
writeNumber(treeCount, output);
if (treeCount <= 0) {
return;
}
/**
* Sort the trees in ancestral order,
* which writes the tree order to the output
*/
ElementTree[] sortedTrees = sortTrees(trees, output);
/* Write the complete tree */
writeTree(sortedTrees[0], path, depth, output);
/* Write the deltas for each of the remaining trees */
for (int i = 1; i < treeCount; i++) {
writeDelta(sortedTrees[i], sortedTrees[i - 1], path, depth, output, comparator);
}
}
/**
* Writes an integer in a compact format biased towards
* small non-negative numbers. Numbers between
* 0 and 254 inclusive occupy 1 byte; other numbers occupy 5 bytes.
*/
protected void writeNumber(int number, DataOutput output) throws IOException {
if (number >= 0 && number < 0xff) {
output.writeByte(number);
} else {
output.writeByte(0xff);
output.writeInt(number);
}
}
/**
* Writes all or some of an element tree to an output stream.
* This always writes the most current version of the element tree
* file format, whereas the reader supports multiple versions.
*
* @param tree The tree to write
* @param path The path of the subtree to write. All nodes on the path above
* the subtree are represented as empty nodes.
* @param depth The depth of the subtree to write. A depth of zero writes a
* single node, and a depth of D_INFINITE writes the whole subtree.
* @param output The stream to write the subtree to.
*/
public void writeTree(ElementTree tree, IPath path, int depth, final DataOutput output) throws IOException {
/* Write the format version number. */
writeNumber(CURRENT_FORMAT, output);
/* This actually just copies the root node, which is what we want */
DeltaDataTree subtree = new DeltaDataTree(tree.getDataTree().copyCompleteSubtree(Path.ROOT));
dataTreeWriter.writeTree(subtree, path, depth, output);
}
}