package com.intellij.flex.uiDesigner.abc;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.text.CharArrayUtil;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.channels.FileChannel;
abstract public class AbcTranscoder extends SwfTranscoder {
public static final TObjectHashingStrategy<CharSequence> HASHING_STRATEGY = new HashingStrategy();
protected FileChannel channel;
protected final TransientString transientNameString = new TransientString();
protected int lastWrittenPosition;
protected void processTags(@Nullable final Condition<CharSequence> abcNameFilter) throws IOException {
while (buffer.position() < buffer.limit()) {
final int tagCodeAndLength = buffer.getShort();
final int type = tagCodeAndLength >> 6;
int length = tagCodeAndLength & 0x3F;
if (length == 63) {
length = buffer.getInt();
}
switch (type) {
case TagTypes.DoABC2:
readAbcName(buffer.position() + 4);
//System.out.print("\n" + transientNameString.toString() + " \n");
if (abcNameFilter != null && !abcNameFilter.value(transientNameString)) {
skipTag(length);
}
else {
int oldPosition = buffer.position();
writeDataBeforeTag(length);
buffer.position(oldPosition);
doAbc2(length);
buffer.position(lastWrittenPosition);
}
continue;
case TagTypes.End:
processEnd(length);
return;
case TagTypes.SymbolClass:
processSymbolClass(length);
continue;
case TagTypes.ExportAssets:
processExportAssets(length, false);
continue;
default:
final int s = processTag(type, length);
if (s == 1) {
skipTag(length);
}
else if (s == 0) {
buffer.position(buffer.position() + length);
}
}
}
}
protected abstract void processSymbolClass(int length) throws IOException;
protected abstract void processEnd(int length) throws IOException;
protected abstract void doAbc2(int length) throws IOException;
protected int processTag(int type, int length) throws IOException {
switch (type) {
case TagTypes.EnableDebugger:
case TagTypes.EnableDebugger2:
case TagTypes.SetBackgroundColor:
case TagTypes.ProductInfo:
case TagTypes.DebugID:
case TagTypes.ScriptLimits:
case TagTypes.Metadata:
return 1;
default:
return 0;
}
}
protected void processExportAssets(int length, boolean readMainSymbolName) throws IOException {
final int bodyPosition = buffer.position();
writeDataBeforeTag(length);
buffer.position(bodyPosition);
final int numSymbols = buffer.getShort();
if (numSymbols == 0) {
return;
}
ensureExportAssetsStorageCreated(numSymbols);
for (int i = 0; i < numSymbols; i++) {
int id = buffer.getShort();
int start = buffer.position();
final int nameLength;
if (readMainSymbolName && id == 0) {
readAbcName(start);
nameLength = transientNameString.length;
}
else {
nameLength = skipAbcName(start);
}
int end = start + nameLength + 1;
storeExportAsset(id, start, end, readMainSymbolName);
buffer.position(end);
}
}
protected abstract void ensureExportAssetsStorageCreated(int numSymbols);
protected abstract void storeExportAsset(int id, int start, int end, boolean mainNameRead);
protected BufferWrapper createBufferWrapper(int length) {
final int off = 4 + transientNameString.length() + 1;
return new BufferWrapper(buffer.array(), buffer.position() + off, length - off);
}
protected boolean writeDataBeforeTag(int length) throws IOException {
int headerLength = length < 63 ? 2 : 6;
int limit = buffer.position() - headerLength;
if (limit == lastWrittenPosition) {
lastWrittenPosition += length + headerLength;
return false;
}
buffer.limit(limit);
buffer.position(lastWrittenPosition);
channel.write(buffer);
lastWrittenPosition = buffer.limit() + length + headerLength;
buffer.limit(buffer.capacity());
return true;
}
protected void skipTag(int tagLength) throws IOException {
writeDataBeforeTag(tagLength);
buffer.position(lastWrittenPosition);
}
protected void readAbcName(final int start) {
int end = start;
byte[] array = buffer.array();
int lastSlashPosition = -1;
byte c;
@SuppressWarnings("MismatchedReadAndWriteOfArray")
final char[] chars = transientNameString.chars;
int index = 0;
while ((c = array[end++]) != 0) {
switch (c) {
case '/':
lastSlashPosition = index;
chars[index] = '.';
break;
default:
chars[index] = (char)c;
}
index++;
}
if (lastSlashPosition != -1) {
chars[lastSlashPosition] = ':';
}
transientNameString.hash = 0;
transientNameString.length = index;
}
protected static final class TransientString implements CharSequence {
private final char[] chars = new char[256];
private int length;
private int hash;
@Override
public int length() {
return length;
}
@Override
public char charAt(int index) {
return chars[index];
}
@Override
public CharSequence subSequence(int start, int end) {
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CharSequence) {
return StringUtil.equals(this, (CharSequence)obj);
}
else {
return super.equals(obj);
}
}
public char[] cloneChars() {
char[] clonedChars = new char[length];
System.arraycopy(chars, 0, clonedChars, 0, length);
return clonedChars;
}
public boolean same(char[] name) {
return CharArrayUtil.equals(chars, 0, chars.length, name, 0, name.length);
}
@NotNull
@Override
public String toString() {
return new String(chars, 0, length);
}
@Override
public int hashCode() {
if (hash == 0 && length > 0) {
hash = StringUtil.stringHashCode(chars, 0, length);
}
return hash;
}
}
private static final class HashingStrategy implements TObjectHashingStrategy<CharSequence> {
@Override
public int computeHashCode(CharSequence object) {
return object.hashCode();
}
@Override
public boolean equals(CharSequence o1, CharSequence o2) {
// must be o2.equals(o1) because o1 is String (cannot equals) and o2 is TransientString or com.intellij.util.text.CharSequenceBackedByArray
return o1 instanceof String ? o2.equals(o1) : o1.equals(o2);
}
}
}