/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.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() {
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);
}
}
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 -> Vector of Integers(indices in trees array) */
HashMap table= new HashMap(numTrees * 2 + 1);
for (int i= 0; i < trees.length; i++) {
List indices= (List)table.get(trees[i]);
if (indices == null) {
indices= new ArrayList();
table.put(trees[i], indices);
}
indices.add(new Integer(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 indices= (List)table.remove(oldest);
for (Enumeration e= Collections.enumeration(indices); e.hasMoreElements();) {
Integer next= (Integer)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) {
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);
}
}