/******************************************************************************
* Copyright (C) 2013 Fabio Zadrozny
*
* 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:
* Fabio Zadrozny <fabiofz@gmail.com> - initial API and implementation
******************************************************************************/
package org.python.pydev.shared_core.partitioner;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentPartitionerExtension2;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TypedPosition;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.python.pydev.shared_core.log.Log;
/**
* A reader that'll only read based on a given partition type.
*
* @author Fabio Zadrozny
*/
public class PartitionCodeReader implements ICharacterScanner, IMarkScanner {
/**
* Note: not suitable for sub-partitions.
*/
public static final String ALL_CONTENT_TYPES_AVAILABLE = "ALL_CONTENT_TYPES_AVAILABLE";
/** The EOF character */
public static final int EOF = -1;
private boolean fForward = false;
private IDocument fDocument;
private int fOffset;
private int fEnd = -1;
private final String contentType;
private int fcurrentPositionI = 0;
private Position fCurrentPosition = null;
private Position[] fPositions;
private char[][] fDelimiters;
private int fStartForwardOffset;
private boolean fSupportKeepPositions;
public PartitionCodeReader(String contentType) {
this.contentType = contentType;
}
/**
* Returns the offset of the last read character. Should only be called after read has been called.
*/
public int getOffset() {
return fForward ? fOffset - 1 : fOffset + 1;
}
public void configureForwardReaderKeepingPositions(int offset, int end) throws IOException,
BadPositionCategoryException {
if (!fSupportKeepPositions) {
throw new AssertionError("configureForwardReader must be called with supportKeepPositions=true.");
}
fOffset = offset;
fStartForwardOffset = offset;
fForward = true;
fEnd = Math.min(fDocument.getLength(), end);
fcurrentPositionI = 0;
if (fPositions.length > 0) {
fCurrentPosition = fPositions[0];
} else {
fCurrentPosition = null;
}
}
public void configureForwardReader(IDocument document, int offset, int end) throws IOException,
BadPositionCategoryException {
configureForwardReader(document, offset, end, false);
}
public void configureForwardReader(IDocument document, int offset, int end, boolean supportKeepPositions)
throws IOException,
BadPositionCategoryException {
fSupportKeepPositions = supportKeepPositions;
fDocument = document;
fOffset = offset;
fStartForwardOffset = offset;
fForward = true;
fEnd = Math.min(fDocument.getLength(), end);
fcurrentPositionI = 0;
fPositions = createPositions(document);
if (fPositions.length > 0) {
fCurrentPosition = fPositions[0];
} else {
fCurrentPosition = null;
}
}
public void configureBackwardReader(IDocument document, int offset) throws IOException,
BadPositionCategoryException {
fStartForwardOffset = 0;
fDocument = document;
fOffset = offset;
fForward = false;
fcurrentPositionI = 0;
fPositions = createPositions(document);
if (fPositions.length > 0) {
fCurrentPosition = fPositions[0];
} else {
fCurrentPosition = null;
}
}
private Position[] createPositions(IDocument document) throws BadPositionCategoryException {
Position[] positions = getDocumentTypedPositions(document, contentType);
List<TypedPosition> typedPositions = PartitionMerger.sortAndMergePositions(positions, document.getLength());
int size = typedPositions.size();
List<Position> list = new ArrayList<Position>(size);
for (int i = 0; i < size; i++) {
Position position = typedPositions.get(i);
if (isPositionValid(position, contentType)) {
list.add(position);
}
}
if (!fForward) {
Collections.reverse(list);
}
Position[] ret = list.toArray(new Position[list.size()]);
return ret;
}
private boolean isPositionValid(Position position, String contentType) {
if (fSupportKeepPositions || (fForward && position.getOffset() + position.getLength() >= fOffset || !fForward
&& position.getOffset() <= fOffset)) {
if (position instanceof TypedPosition) {
TypedPosition typedPosition = (TypedPosition) position;
if (contentType != null && !contentType.equals(ALL_CONTENT_TYPES_AVAILABLE)) {
if (!contentType.equals(typedPosition.getType())) {
return false;
}
}
}
return true;
}
return false;
}
/**
* Note: this just gets the positions in the document. To cover for holes, use
* StringUtils.sortAndMergePositions with the result of this call.
*/
public static Position[] getDocumentTypedPositions(IDocument document, String defaultContentType) {
if (ALL_CONTENT_TYPES_AVAILABLE.equals(defaultContentType)) {
//Consider the whole document
return new Position[] { new TypedPosition(0, document.getLength(), defaultContentType) };
}
Position[] positions;
try {
IDocumentPartitionerExtension2 partitioner = (IDocumentPartitionerExtension2) document
.getDocumentPartitioner();
String[] managingPositionCategories = partitioner.getManagingPositionCategories();
Assert.isTrue(managingPositionCategories.length == 1);
positions = document.getPositions(managingPositionCategories[0]);
if (positions == null) {
positions = new Position[] { new TypedPosition(0, document.getLength(), defaultContentType) };
}
} catch (Exception e) {
Log.log("Unable to get positions for: " + defaultContentType, e); //Shouldn't happen, but if it does, consider the whole doc.
positions = new Position[] { new TypedPosition(0, document.getLength(), defaultContentType) };
}
return positions;
}
/*
* @see Reader#close()
*/
public void close() throws IOException {
fDocument = null;
}
/*
* @see SingleCharReader#read()
*/
@Override
public int read() {
try {
return fForward ? readForwards() : readBackwards();
} catch (BadLocationException x) {
return EOF; //Document may have changed...
}
}
@Override
public char[][] getLegalLineDelimiters() {
if (fDelimiters == null) {
String[] delimiters = fDocument.getLegalLineDelimiters();
fDelimiters = new char[delimiters.length][];
for (int i = 0; i < delimiters.length; i++) {
fDelimiters[i] = delimiters[i].toCharArray();
}
}
return fDelimiters;
}
@Override
public int getColumn() {
try {
final int offset = getOffset();
final int line = fDocument.getLineOfOffset(offset);
final int start = fDocument.getLineOffset(line);
return offset - start;
} catch (BadLocationException e) {
}
return -1;
}
@Override
public void unread() {
if (fForward) {
if (fCurrentPosition == null) { //unread EOF
if (fPositions.length > 0) {
fcurrentPositionI = fPositions.length - 1;
fCurrentPosition = fPositions[fcurrentPositionI];
fOffset = fCurrentPosition.offset + fCurrentPosition.length;
if (fOffset < fStartForwardOffset) {
fOffset = fStartForwardOffset;
}
}
return;
}
fOffset--;
if (fOffset < fStartForwardOffset) {
fOffset = fStartForwardOffset;
return;
}
while (true) {
if (fOffset < fCurrentPosition.offset) {
if (fcurrentPositionI > 0) {
fcurrentPositionI--;
fCurrentPosition = fPositions[fcurrentPositionI];
if (fCurrentPosition.offset + fCurrentPosition.length <= fOffset) {
fOffset = fCurrentPosition.offset + fCurrentPosition.length - 1;
if (fOffset < fStartForwardOffset) {
fOffset = fStartForwardOffset;
}
return;
}
} else {
return;
}
} else {
break;
}
}
} else {
throw new AssertionError("Unread when going backward not supported yet");
}
}
private int readForwards() throws BadLocationException {
while (true) {
if (fCurrentPosition == null) {
return EOF;
}
if (fCurrentPosition.offset + fCurrentPosition.length <= fOffset) {
fcurrentPositionI++;
if (fcurrentPositionI >= fPositions.length) {
fCurrentPosition = null;
return EOF;
}
fCurrentPosition = fPositions[fcurrentPositionI];
} else {
break;
}
}
if (fCurrentPosition.offset > fOffset) {
fOffset = fCurrentPosition.offset;
}
if (fOffset < fEnd) {
char current = fDocument.getChar(fOffset);
fOffset++;
return current;
} else {
fCurrentPosition = null;
}
return EOF;
}
private int readBackwards() throws BadLocationException {
while (true) {
if (fCurrentPosition == null) {
return EOF;
}
if (fCurrentPosition.offset > fOffset) {
fcurrentPositionI++;
if (fcurrentPositionI >= fPositions.length) {
return EOF;
}
fCurrentPosition = fPositions[fcurrentPositionI];
} else {
break;
}
}
if (fCurrentPosition.offset + fCurrentPosition.length <= fOffset) {
fOffset = fCurrentPosition.offset + fCurrentPosition.length - 1;
}
if (fOffset >= 0) {
char current = fDocument.getChar(fOffset);
fOffset--;
return current;
}
return EOF;
}
@Override
public int getMark() {
return fOffset;
}
@Override
public void setMark(int offset) {
if (fForward) {
fCurrentPosition = null;
for (int i = 0; i < fPositions.length; i++) {
Position p = fPositions[i];
if (p.offset + p.length > offset) {
fcurrentPositionI = i;
fCurrentPosition = p;
fOffset = offset;
break;
}
}
} else {
fCurrentPosition = null;
for (int i = 0; i < fPositions.length; i++) { //note: it's already backwards
Position p = fPositions[i];
if (p.offset < offset) {
fcurrentPositionI = i;
fCurrentPosition = p;
fOffset = offset;
break;
}
}
}
}
}