/*******************************************************************************
* Copyright (c) Jun 27, 2011 Zend Technologies Ltd.
* 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
*******************************************************************************/
package org.zend.sdklib.mapping;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import org.zend.sdklib.internal.mapping.Mapping;
import org.zend.sdklib.internal.mapping.MappingEntry;
import org.zend.sdklib.mapping.IMappingEntry.Type;
/**
* Abstract implementation of {@link IMappingLoader}. It is basic loader for
* resource mapping stored in a properties file.
*
* @author Wojciech Galanciak, 2011
*
*/
public abstract class PropertiesBasedMappingLoader implements IMappingLoader {
private static final String TAB = "\t";
public static final String EXCLUDES = ".excludes";
public static final String SEPARATOR = ",";
public static final String INCLUDES = ".includes";
public static final String GLOBAL = "**/";
final String EOL = System.getProperty("line.separator");
private class LineReader {
public LineReader(InputStream inStream) {
this.inStream = inStream;
inByteBuf = new byte[8192];
}
byte[] inByteBuf;
char[] inCharBuf;
char[] lineBuf = new char[1024];
int inLimit = 0;
int inOff = 0;
InputStream inStream;
Reader reader;
int readLine() throws IOException {
int len = 0;
char c = 0;
boolean skipWhiteSpace = true;
boolean isCommentLine = false;
boolean isNewLine = true;
boolean appendedLineBegin = false;
boolean precedingBackslash = false;
boolean skipLF = false;
while (true) {
if (inOff >= inLimit) {
inLimit = (inStream == null) ? reader.read(inCharBuf)
: inStream.read(inByteBuf);
inOff = 0;
if (inLimit <= 0) {
if (len == 0 || isCommentLine) {
return -1;
}
return len;
}
}
if (inStream != null) {
// The line below is equivalent to calling a
// ISO8859-1 decoder.
c = (char) (0xff & inByteBuf[inOff++]);
} else {
c = inCharBuf[inOff++];
}
if (skipLF) {
skipLF = false;
if (c == '\n') {
continue;
}
}
if (skipWhiteSpace) {
if (c == ' ' || c == '\t' || c == '\f') {
continue;
}
if (!appendedLineBegin && (c == '\r' || c == '\n')) {
continue;
}
skipWhiteSpace = false;
appendedLineBegin = false;
}
if (isNewLine) {
isNewLine = false;
if (c == '#' || c == '!') {
isCommentLine = true;
continue;
}
}
if (c != '\n' && c != '\r') {
lineBuf[len++] = c;
if (len == lineBuf.length) {
int newLength = lineBuf.length * 2;
if (newLength < 0) {
newLength = Integer.MAX_VALUE;
}
char[] buf = new char[newLength];
System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length);
lineBuf = buf;
}
// flip the preceding backslash flag
if (c == '\\') {
precedingBackslash = !precedingBackslash;
} else {
precedingBackslash = false;
}
} else {
// reached EOL
if (isCommentLine || len == 0) {
isCommentLine = false;
isNewLine = true;
skipWhiteSpace = true;
len = 0;
continue;
}
if (inOff >= inLimit) {
inLimit = (inStream == null) ? reader.read(inCharBuf)
: inStream.read(inByteBuf);
inOff = 0;
if (inLimit <= 0) {
return len;
}
}
if (precedingBackslash) {
len -= 1;
// skip the leading whitespace characters in following
// line
skipWhiteSpace = true;
appendedLineBegin = true;
precedingBackslash = false;
if (c == '\r') {
skipLF = true;
}
} else {
return len;
}
}
}
}
}
/*
* (non-Javadoc)
*
* @see org.zend.sdklib.mapping.IMappingLoader#load(java.io.InputStream)
*/
@Override
public List<IMappingEntry> load(InputStream stream) throws IOException {
List<IMappingEntry> mapping = new ArrayList<IMappingEntry>();
if (stream != null) {
mapping = loadMapping(stream);
stream.close();
}
return mapping;
}
/*
* (non-Javadoc)
*
* @see
* org.zend.sdklib.mapping.IMappingLoader#store(org.zend.sdklib.mapping.
* IResourceMapping, java.io.File)
*/
@Override
public void store(IMappingModel model, File output) throws IOException {
byte[] bytes = getByteArray(model);
OutputStream out = new FileOutputStream(output);
out.write(bytes);
out.close();
}
protected List<IMapping> getMappings(String[] result) throws IOException {
List<IMapping> mappings = new ArrayList<IMapping>();
for (int i = 0; i < result.length; i++) {
String file = result[i].trim();
if (file.isEmpty()) {
continue;
}
boolean isGlobal = file.startsWith(GLOBAL);
if (isGlobal) {
file = file.substring(GLOBAL.length());
}
mappings.add(new Mapping(file, isGlobal));
}
return mappings;
}
protected byte[] getByteArray(IMappingModel model) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
List<IMappingEntry> entries = model.getEnties();
for (IMappingEntry entry : entries) {
String entryString = getEntry(entry);
result.write(entryString.getBytes());
result.write(EOL.getBytes());
}
return result.toByteArray();
}
protected List<IMappingEntry> loadMapping(InputStream stream)
throws IOException {
LineReader reader = new LineReader(stream);
List<IMappingEntry> result = new ArrayList<IMappingEntry>();
char[] convtBuf = new char[1024];
int limit;
int keyLen;
int valueStart;
char c;
boolean hasSep;
boolean precedingBackslash;
while ((limit = reader.readLine()) >= 0) {
c = 0;
keyLen = 0;
valueStart = limit;
hasSep = false;
precedingBackslash = false;
while (keyLen < limit) {
c = reader.lineBuf[keyLen];
// need check if escaped.
if ((c == '=' || c == ':') && !precedingBackslash) {
valueStart = keyLen + 1;
hasSep = true;
break;
} else if ((c == ' ' || c == '\t' || c == '\f')
&& !precedingBackslash) {
valueStart = keyLen + 1;
break;
}
if (c == '\\') {
precedingBackslash = !precedingBackslash;
} else {
precedingBackslash = false;
}
keyLen++;
}
while (valueStart < limit) {
c = reader.lineBuf[valueStart];
if (c != ' ' && c != '\t' && c != '\f') {
if (!hasSep && (c == '=' || c == ':')) {
hasSep = true;
} else {
break;
}
}
valueStart++;
}
String key = loadConvert(reader.lineBuf, 0, keyLen, convtBuf);
String value = loadConvert(reader.lineBuf, valueStart, limit
- valueStart, convtBuf);
if (key != null && value != null) {
String[] files = value.trim().split(SEPARATOR);
List<IMapping> mappings = getMappings(files);
String folderName = key.substring(0, key.indexOf("."));
String kind = key.substring(key.indexOf(".")).trim();
Type type = INCLUDES.equals(kind) ? Type.INCLUDE : Type.EXCLUDE;
result.add(new MappingEntry(folderName, mappings, type));
}
}
return result;
}
private String getEntry(IMappingEntry entry) {
StringBuilder result = new StringBuilder();
result.append(entry.getFolder());
result.append(entry.getType() == Type.INCLUDE ? INCLUDES : EXCLUDES);
result.append(" = ");
result.append(getValue(entry.getMappings()));
return result.toString();
}
private String getValue(List<IMapping> mappings) {
StringBuilder result = new StringBuilder();
int size = mappings.size() - 1;
for (IMapping entry : mappings) {
String file = entry.getPath();
if (entry.isGlobal()) {
file = GLOBAL + file;
}
result.append(file);
if (size-- > 0) {
result.append(SEPARATOR);
result.append("\\");
result.append(EOL);
result.append(TAB);
result.append(TAB);
}
}
return result.toString();
}
/*
* Converts encoded \uxxxx to unicode chars and changes special saved
* chars to their original forms
*/
private String loadConvert(char[] in, int off, int len, char[] convtBuf) {
if (convtBuf.length < len) {
int newLen = len * 2;
if (newLen < 0) {
newLen = Integer.MAX_VALUE;
}
convtBuf = new char[newLen];
}
char aChar;
char[] out = convtBuf;
int outLen = 0;
int end = off + len;
while (off < end) {
aChar = in[off++];
if (aChar == '\\') {
aChar = in[off++];
if (aChar == 'u') {
// Read the xxxx
int value = 0;
for (int i = 0; i < 4; i++) {
aChar = in[off++];
switch (aChar) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
value = (value << 4) + aChar - '0';
break;
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
value = (value << 4) + 10 + aChar - 'a';
break;
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
value = (value << 4) + 10 + aChar - 'A';
break;
default:
throw new IllegalArgumentException(
"Malformed \\uxxxx encoding.");
}
}
out[outLen++] = (char) value;
} else {
if (aChar == 't')
aChar = '\t';
else if (aChar == 'r')
aChar = '\r';
else if (aChar == 'n')
aChar = '\n';
else if (aChar == 'f')
aChar = '\f';
out[outLen++] = aChar;
}
} else {
out[outLen++] = aChar;
}
}
return new String(out, 0, outLen);
}
}