package org.erlide.erlang.content;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.internal.content.Util;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.ITextContentDescriber;
import com.google.common.base.Charsets;
public class ErlangContentDescriber implements ITextContentDescriber {
private static final QualifiedName[] SUPPORTED_OPTIONS = new QualifiedName[] {
IContentDescription.CHARSET, IContentDescription.BYTE_ORDER_MARK };
private static final String PREFIX = "-encoding"; //$NON-NLS-1$
private static final String SUFFIX = "."; //$NON-NLS-1$
private static final String BOM = "ErlangContentDescriber.bom"; //$NON-NLS-1$
private static final String CHARSET = "ErlangContentDescriber.charset"; //$NON-NLS-1$
private static final String RESULT = "ErlangContentDescriber.processed"; //$NON-NLS-1$
@Override
public int describe(final InputStream input,
final IContentDescription description) throws IOException {
return describe2(input, description, new HashMap<String, Object>());
}
int describe2(final InputStream input,
final IContentDescription description,
final Map<String, Object> properties) throws IOException {
if (!isProcessed(properties)) {
fillContentProperties(input, description, properties);
}
return internalDescribe(description, properties);
}
@Override
public int describe(final Reader input,
final IContentDescription description) throws IOException {
return describe2(input, description, new HashMap<String, Object>());
}
int describe2(final Reader input, final IContentDescription description,
final Map<String, Object> properties) throws IOException {
if (!isProcessed(properties)) {
fillContentProperties(readEncoding(input), description, properties);
}
return internalDescribe(description, properties);
}
private boolean isProcessed(final Map<String, Object> properties) {
final Boolean result = (Boolean) properties.get(RESULT);
if (result != null) {
return true;
}
return false;
}
private void fillContentProperties(final InputStream input,
final IContentDescription description,
final Map<String, Object> properties) throws IOException {
final byte[] bom = Util.getByteOrderMark(input);
String encoding = "UTF-8"; //$NON-NLS-1$
input.reset();
if (bom != null) {
if (bom == IContentDescription.BOM_UTF_16BE) {
encoding = "UTF-16BE"; //$NON-NLS-1$
} else if (bom == IContentDescription.BOM_UTF_16LE) {
encoding = "UTF-16LE"; //$NON-NLS-1$
}
// skip BOM to make comparison simpler
input.skip(bom.length);
properties.put(BOM, bom);
}
fillContentProperties(readEncoding(input, encoding), description,
properties);
}
private void fillContentProperties(final String charset,
final IContentDescription description,
final Map<String, Object> properties) throws IOException {
if (charset != null) {
properties.put(CHARSET, charset);
}
properties.put(RESULT, new Boolean(true));
}
private int internalDescribe(final IContentDescription description,
final Map<String, Object> properties) {
if (description != null) {
final byte[] bom = (byte[]) properties.get(BOM);
if (bom != null
&& description
.isRequested(IContentDescription.BYTE_ORDER_MARK)) {
description.setProperty(IContentDescription.BYTE_ORDER_MARK,
bom);
}
}
if (description == null) {
return VALID;
}
final String charset = (String) properties.get(CHARSET);
if (description.isRequested(IContentDescription.CHARSET)) {
if (charset != null && !isCharsetValid(charset)) {
return INVALID;
}
description.setProperty(IContentDescription.CHARSET,
realCharsetName(charset));
}
return VALID;
}
private String realCharsetName(final String charset) {
if ("latin1".equals(charset)) {
return Charsets.ISO_8859_1.name();
} else if ("utf8".equals(charset)) {
return Charsets.UTF_8.name();
} else {
return Charsets.ISO_8859_1.name();
}
}
private boolean isCharsetValid(final String charset) {
return "latin1".equals(charset) || "utf8".equals(charset);
}
private String readEncoding(final InputStream input, final String encoding)
throws IOException {
String line = null;
while ((line = readLine(input)) != null) {
final String decl = getDeclaration(line);
if (decl == null) {
return null;
}
if (decl.length() > 0) {
return decl;
}
}
return null;
}
private String readEncoding(final Reader input) throws IOException {
final BufferedReader reader = new BufferedReader(input);
String line = null;
while ((line = reader.readLine()) != null) {
final String decl = getDeclaration(line);
if (decl == null) {
return null;
}
if (decl.length() > 0) {
return decl;
}
}
return null;
}
/**
* @param line
* @return null if search should be canceled; "" if nothing found yet;
* String if found
*/
private String getDeclaration(String line) {
line = line.trim();
if (line.indexOf(PREFIX) != -1) {
String decl = line.substring(
line.indexOf(PREFIX) + PREFIX.length(),
line.indexOf(SUFFIX)).trim();
if (decl.startsWith("(")) {
decl = decl.substring(1, decl.length() - 1).trim();
}
return decl;
} else if (!(line.length() == 0 || line.startsWith("-module") || line
.startsWith("%"))) {
// cancel search if we find non-attributes
return null;
}
return "";
}
@Override
public QualifiedName[] getSupportedOptions() {
return SUPPORTED_OPTIONS;
}
/**
* Read a line of data from the underlying InputStream
*
* @return a line stripped of line terminators
*/
public String readLine(final InputStream in) throws IOException {
final int _CR = 13;
final int _LF = 10;
int _ch = -1; // currently read char
final StringBuffer sb = new StringBuffer("");
_ch = in.read();
while (_ch != _CR && _ch != _LF && _ch != -1) {
sb.append((char) _ch);
_ch = in.read();
}
if (_ch == -1) {
return null;
}
return new String(sb);
}
}