package org.eclipse.assemblyformatter.ir;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.assemblyformatter.ir.lowlevel.CharacterLiteral;
import org.eclipse.assemblyformatter.ir.lowlevel.Comment;
import org.eclipse.assemblyformatter.ir.lowlevel.IntegerLiteral;
import org.eclipse.assemblyformatter.ir.lowlevel.LineSeparator;
import org.eclipse.assemblyformatter.ir.lowlevel.Symbol;
import org.eclipse.assemblyformatter.ir.lowlevel.WhiteSpace;
import org.eclipse.assemblyformatter.ir.lowlevel.Comment.Type;
import org.eclipse.assemblyformatter.parsers.IARParser;
import org.eclipse.assemblyformatter.parsers.Parser;
import org.w3c.dom.DOMException;
/**
* General formatting procedure:
* <ol>
* <li>Identification of low-level elements (tokens, using automata).</li>
* <li>Identification of high-level elements (using rules).</li>
* <li>Resizing of white-space sections.</li>
* </ol>
*
*/
public class Formatter {
private IDocument document;
private Section base;
public Formatter(IDocument document) {
this.document = document;
}
public void tokenize() {
Tokenizer tokenizer = new Tokenizer();
tokenizer.setContent(document.get());
base = tokenizer.run();
}
/**
*
* @throws BadLocationException
*/
public void parse() throws BadLocationException {
Parser parser = new IARParser();
parser.setBase(base);
parser.setDocument(document);
parser.run();
}
/**
* Align document content.
*
* Call this after parsing.
*
* <h3>Aligning procedure</h3>
* <ol>
* <li>Determine distances between vertical lines. (Pass through section
* list and see the longest label, instruction, etc.)</li>
* <li>Resize white-space sections.</li>
* </ol>
*
* <table border="1" cellpadding="4">
* <tbody>
* <tr>
* <th>WHITE_SPACE between ...</th>
* <th>WHITE_SPACE length</th>
* </tr>
* <tr>
* <td>LINE_SEPARATOR [+ LABEL] and INSTRUCTION</td>
* <td>instructionDistance [- LABEL length]</td>
* </tr>
* <tr>
* <td>INSTRUCTION and PARAMETER</td>
* <td>parameterDistance - INSTRUCTION length</td>
* </tr>
* </tbody>
* </table>
*
* TODO Label alignment to column 0
*
* @throws BadLocationException
*/
public void rewrite() throws BadLocationException {
Section section = null;
// Determine distances between vertical lines.
// TODO Case when no label is found
int instructionDistance = 0;
int parameterDistance = 0;
int commentDistance = 0;
section = base;
while (section != null) {
Section nextSection = section.getNextSection();
final int length = section.getLength();
if (length > 0) {
// instruction distance
if (section instanceof Label) {
if (instructionDistance < length) {
instructionDistance = length;
}
} else {
// parameter distance
if (section instanceof Instruction) {
if (parameterDistance < length) {
parameterDistance = length;
}
}
// comment distance
if (section instanceof Parameter) {
if (commentDistance < length) {
commentDistance = length;
}
}
}
}
section = nextSection;
}
final int reserve = 4;
// Resize white-space sections.
section = base;
while (section != null) {
Section nextSection = section.getNextSection();
if (nextSection == null) {
break; // Exit loop.
}
if (section instanceof LineSeparator) {
// Instruction aligning
final int distance = instructionDistance + reserve;
if (nextSection instanceof Label) {
if (nextSection.nextIs(WhiteSpace.class, Instruction.class)) {
resize(nextSection,
(WhiteSpace) Section.getNextIs__staticData(0),
distance);
} else {
if (nextSection.nextIs(Instruction.class)) {
resize(nextSection, null, distance);
}
}
} else {
if (section.nextIs(WhiteSpace.class, Instruction.class)) {
WhiteSpace whiteSpaceSection = (WhiteSpace) Section
.getNextIs__staticData(0);
whiteSpaceSection.setVirtualLength(distance);
}
}
// Go forward.
section = nextSection;
continue;
}
if (section instanceof Instruction) {
// Parameter aligning
if (section.nextIs(WhiteSpace.class)) {
if (nextSection.nextIs(Parameter.class)) {
resize(section, (WhiteSpace) nextSection,
parameterDistance + reserve);
} else {
// Comment aligning
if (nextSection.nextIs(Comment.class)) {
resize(section, (WhiteSpace) nextSection,
parameterDistance + reserve
+ commentDistance + reserve);
}
}
}
// Go forward.
section = nextSection;
continue;
}
if (section instanceof Parameter) {
// Comment aligning
final int distance = commentDistance + reserve;
if (section.nextIs(WhiteSpace.class, Comment.class)) {
resize(section, (WhiteSpace) nextSection, distance);
}
}
section = nextSection;
}
// Connect position objects to document.
section = base;
while (section != null) {
Section nextSection = section.getNextSection();
try {
document.addPosition(section.getPosition());
} catch (BadLocationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
section = nextSection;
}
// Rewrite white-space sections.
section = base;
while (section != null) {
Section nextSection = section.getNextSection();
if (section instanceof WhiteSpace) {
WhiteSpace whiteSpace = (WhiteSpace) section;
if (whiteSpace.getVirtualLength() >= 0) {
int offset = whiteSpace.getOffset();
int length = whiteSpace.getLength();
String text = whiteSpace.getVirtualString();
try {
document.replace(offset, length, text);
} catch (BadLocationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
section = nextSection;
}
// Normalize comments.
// In case ;... make ; ...
int offset;
section = base;
while (section != null) {
Section nextSection = section.getNextSection();
if (section instanceof Comment) {
Comment comment = (Comment) section;
switch (comment.getType()) {
case A:
offset = comment.getOffset() + 1;
if (document.getChar(offset) != ' ') {
document.replace(offset, 0, " ");
}
break;
case CPP:
offset = comment.getOffset() + 2;
if (document.getChar(offset) != ' ') {
document.replace(offset, 0, " ");
}
break;
}
}
section = nextSection;
}
// Disconnect position objects from document.
section = base;
while (section != null) {
Section nextSection = section.getNextSection();
document.removePosition(section.getPosition());
section = nextSection;
}
}
/**
*
* @param section
* @param whiteSpace
* can be null
* @param whiteSpaceLength
*/
private void resize(Section section, WhiteSpace whiteSpace,
int whiteSpaceLength) {
final int sectionLength = section.getLength();
// Null WHITE_SPACE section case.
if (whiteSpace == null) {
whiteSpace = new WhiteSpace();
whiteSpace.setOffset(section.getOffset() + sectionLength);
whiteSpace.setLength(0);
whiteSpace.setNextSection(section.getNextSection());
section.setNextSection(whiteSpace);
}
whiteSpace.setVirtualLength(whiteSpaceLength - sectionLength);
}
/**
* Writes the content of the linked list of document sections obtained after
* run().
*
* This is for verification/debugging purposes.
*
* @throws ParserConfigurationException
* @throws IOException
* @throws TransformerException
*/
public void writeSectionList(String filename)
throws ParserConfigurationException, IOException,
TransformerException {
PrintWriter outputStream = null;
try {
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
org.w3c.dom.Document domDocument;
domDocument = builder.newDocument();
// Set document content
org.w3c.dom.Element elementDocument = domDocument
.createElement("document");
domDocument.appendChild(elementDocument);
Section section = base;
while (section != null) {
Section nextSection = section.getNextSection();
org.w3c.dom.Element elementSection = domDocument
.createElement(section.getClass().getSimpleName());
org.w3c.dom.CDATASection cdata;
String sectionContent;
try {
sectionContent = section.getContent(document);
} catch (BadLocationException e) {
sectionContent = e.getClass().getSimpleName();
}
cdata = domDocument.createCDATASection(sectionContent);
elementSection.setAttribute("offset",
Integer.toString(section.getOffset()));
elementSection.setAttribute("length",
Integer.toString(section.getLength()));
if (section instanceof WhiteSpace) {
WhiteSpace whiteSpace = (WhiteSpace) section;
if (whiteSpace.getVirtualLength() >= 0) {
elementSection
.setAttribute("virtualLength",
Integer.toString(whiteSpace
.getVirtualLength()));
}
}
elementSection.appendChild(cdata);
elementDocument.appendChild(elementSection);
section = nextSection;
}
// Use a Transformer for output
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
DOMSource source = new DOMSource(domDocument);
outputStream = new PrintWriter(new FileWriter(filename));
StreamResult result = new StreamResult(outputStream);
transformer.transform(source, result);
} catch (ParserConfigurationException e) {
throw e;
} catch (IOException e) {
throw e;
} catch (TransformerConfigurationException e) {
throw e;
} catch (TransformerException e) {
throw e;
} finally {
if (outputStream != null) {
outputStream.close();
}
}
}
/**
* This procedure replaces any TAB character in the document with a fixed
* number of space characters. Call this procedure before tokenization.
*
* @throws BadLocationException
*/
public void replaceAnyTab(final int spaceCharCount)
throws BadLocationException {
if (spaceCharCount > (1 << 5)/* Maximum allowed space characters */) {
// Error
return;
}
StringBuilder strBuilder = new StringBuilder();
for (int i = 0; i < spaceCharCount; i++) {
strBuilder.append(' ');
}
final String space = strBuilder.toString();
for (int offset = 0; offset < document.getLength(); offset++) {
if (document.getChar(offset) == '\t') {
document.replace(offset, 1, space);
offset += spaceCharCount - 1; // Jump over the space inserted
}
}
}
}