/*
* Copyright (c) 2003-2004 Christopher Lenz and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* Christopher Lenz - initial API and implementation
*
* $Id: CssCodeReader.java,v 1.1 2004-09-02 18:07:13 jsurfer Exp $
*/
package net.sourceforge.phpeclipse.css.core.internal.text;
import java.io.IOException;
import java.io.Reader;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
/**
*
*/
public class CssCodeReader extends Reader {
// Constants ---------------------------------------------------------------
/**
* Constant for configuring a reader to read the document backwards.
*/
public static final int BACKWARD = -1;
/**
* Constant for configuring a reader to read the document forwards.
*/
public static final int FORWARD = 1;
// Instance Variables ------------------------------------------------------
/**
* The document to read. Is null
when the reader is closed.
*/
private IDocument document;
/**
* The direction in which the document should be read. Can be either
* {@link CssCodeReader#BACKWARD} or {@link CssCodeReader#FORWARD}.
*/
private int direction;
/**
* The offset at which to start reading.
*/
private int offset;
/**
* The offset at which to end reading.
*/
private int end = -1;
/**
* Whether comments should be skipped.
*/
private boolean skipComments;
/**
* Whether string literals should be skipped.
*/
private boolean skipStrings;
/**
* Whether whitespace characters should be skipped.
*/
private boolean skipWhitespace;
// Constructors ------------------------------------------------------------
/**
* Constructor.
*
* @param document the document to read
* @param offset the offset at which to start reading
*/
public CssCodeReader(IDocument document, int offset) {
this(document, offset, document.getLength() - offset, FORWARD);
}
/**
* Constructor.
*
* @param document the document to read
* @param offset the offset at which to start reading
* @param length the number of characters to read at most
*/
public CssCodeReader(IDocument document, int offset, int length) {
this(document, offset, length, FORWARD);
}
/**
* Constructor.
*
* @param document the document to read
* @param offset the offset at which to start reading
* @param length the number of characters to read at most
* @param direction the reading direction (either
* {@link CssCodeReader#BACKWARD} or {@link CssCodeReader#FORWARD})
*/
public CssCodeReader(IDocument document, int offset, int length,
int direction) {
this(document, offset, length, direction, true, false, false);
}
/**
* Constructor.
*
* @param document the document to read
* @param offset the offset at which to start reading
* @param length the number of characters to read at most
* @param direction the reading direction (either
* {@link CssCodeReader#BACKWARD} or {@link CssCodeReader#FORWARD})
* @param skipComments whether the reader should skip comments
* @param skipStrings whether the reader should skip strings
* @param skipWhitespace whether the reader should skip whitespace
*/
public CssCodeReader(IDocument document, int offset, int length,
int direction, boolean skipComments, boolean skipStrings,
boolean skipWhitespace) {
if ((direction != BACKWARD) && (direction != FORWARD)) {
throw new IllegalArgumentException();
}
if ((offset < 0) || (offset > document.getLength())) {
throw new IllegalArgumentException();
}
this.document = document;
this.offset = offset;
this.direction = direction;
if (direction == FORWARD) {
end = Math.min(document.getLength(), offset + length);
} else {
end = Math.max(0, offset - length);
}
this.skipComments = skipComments;
this.skipStrings = skipStrings;
this.skipWhitespace = skipWhitespace;
}
// Reader Implementation ---------------------------------------------------
/**
* @see Reader#close()
*/
public void close() {
document = null;
}
/**
* @see Reader#read(char[], int, int)
*/
public int read(char[] cbuf, int off, int len) throws IOException {
for (int i = off; i < off + len; i++) {
int c = read();
if (c == -1) {
if (i == off) {
return -1;
} else {
return i - off;
}
}
cbuf[i] = (char) c;
}
return len;
}
/**
* @see Reader#read()
*/
public int read() throws IOException {
if (document == null) {
throw new IllegalStateException();
}
try {
if (direction == FORWARD) {
return readForwards();
} else {
return readBackwards();
}
} catch (BadLocationException ble) {
throw new IOException(ble.getMessage());
}
}
// Public Methods ----------------------------------------------------------
/**
* Returns the document that is being read.
*
* @return The document
*/
public IDocument getDocument() {
return document;
}
/**
* Returns the offset of the last read character. Should only be called
* after read has been called.
*
* @return The offset of the last read character
*/
public int getOffset() {
return (direction == FORWARD) ? offset - 1 : offset;
}
/**
* Returns whether the reader is currently configured to skip comments.
*
* @return true
if the reader is skipping comments and
* false
otherwise
*/
public boolean isSkippingComments() {
return skipComments;
}
/**
* Configures the reader to skip comment blocks.
*
* @param skipComments Whether comments should be skipped
*/
public void setSkipComments(boolean skipComments) {
this.skipComments = skipComments;
}
/**
* Returns whether the reader is currently configured to skip string
* literals.
*
* @return true
if the reader is skipping strings and
* false
otherwise
*/
public boolean isSkippingStrings() {
return skipStrings;
}
/**
* Configures the reader to skip strings.
*
* @param skipStrings Whether strings should be skipped
*/
public void setSkipStrings(boolean skipStrings) {
this.skipStrings = skipStrings;
}
/**
* Returns whether the reader is currently configured to skip whitespace.
*
* @return true
if the reader is skipping whitespace and
* false
otherwise
*/
public boolean isSkippingWhitespace() {
return skipWhitespace;
}
/**
* Configures the reader to skip whitespace.
*
* @param skipWhitespace Whether whitespace should be skipped
*/
public void setSkipWhitespace(boolean skipWhitespace) {
this.skipWhitespace = skipWhitespace;
}
// Private Methods ---------------------------------------------------------
private int readBackwards() throws BadLocationException {
while (offset > 0) {
offset--;
char current = document.getChar(offset);
switch (current) {
case '/': {
if (skipComments && (offset > 1)) {
char next = document.getChar(offset - 1);
if (next == '*') {
// a comment ends, advance to the comment start
offset -= 2;
skipUntilStartOfComment();
continue;
}
}
break;
}
case '"':
case '\'': {
if (skipStrings) {
offset--;
skipUntilStartOfString(current);
continue;
}
break;
}
default: {
if (skipWhitespace && Character.isWhitespace(current)) {
continue;
}
}
}
return current;
}
return -1;
}
private int readForwards() throws BadLocationException {
while (offset < end) {
char current = document.getChar(offset++);
switch (current) {
case '/': {
if (skipComments && (offset < end)) {
char next = document.getChar(offset);
if (next == '*') {
// a comment starts, advance to the comment end
offset++;
skipUntilEndOfComment();
continue;
}
}
break;
}
case '"':
case '\'': {
if (skipStrings) {
skipUntilEndOfString(current);
continue;
}
break;
}
default: {
if (skipWhitespace && Character.isWhitespace(current)) {
offset++;
continue;
}
}
}
return current;
}
return -1;
}
private void skipUntilStartOfComment() throws BadLocationException {
while (offset > 0) {
char current = document.getChar(offset--);
if ((current == '*') && (0 <= offset)
&& (document.getChar(offset) == '/')) {
return;
}
}
}
private void skipUntilEndOfComment() throws BadLocationException {
while (offset < end) {
char current = document.getChar(offset++);
if (current == '*') {
if ((offset < end)
&& (document.getChar(offset) == '/')) {
offset++;
return;
}
}
}
}
private void skipUntilStartOfString(char delimiter)
throws BadLocationException {
while (offset > 0) {
char current = document.getChar(offset);
if (current == delimiter) {
if (!((0 <= offset) && document.getChar(offset - 1) == '\\')) {
return;
}
}
offset--;
}
}
private void skipUntilEndOfString(char delimiter)
throws BadLocationException {
while (offset < end) {
char current = document.getChar(offset++);
if (current == '\\') {
// ignore escaped characters
offset++;
} else if (current == delimiter) {
return;
}
}
}
}