package com.intellij.flex.uiDesigner.abc;
import com.intellij.openapi.util.Condition;
import gnu.trove.TIntObjectHashMap;
import gnu.trove.TIntObjectIterator;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.util.ArrayList;
/**
* Filter SWF for unresolved definitions. Support only SWF from SWC, i.e. DoABC2 for each script (<DoABC2
* name='org/flyti/plexus/events/DispatcherEvent'>)
* Optimized SWF (merged DoABC2) is not supported.
*/
public class AbcFilter extends AbcTranscoder {
protected final ArrayList<Decoder> decoders = new ArrayList<>();
private final boolean onlyAbcAsTag;
protected TIntObjectHashMap<TagPositionInfo> exportAssets;
protected int symbolsClassTagLengthWithoutUselessMainClass = -1;
protected int sA;
protected int sB;
public AbcFilter() {
this(false);
}
public AbcFilter(boolean onlyAbcAsTag) {
this.onlyAbcAsTag = onlyAbcAsTag;
}
public void filter(File in, File out, @Nullable Condition<CharSequence> abcNameFilter) throws IOException {
//noinspection IOResourceOpenedButNotSafelyClosed
filter(new FileInputStream(in), in.length(), out, abcNameFilter);
}
//public void filter(VirtualFile in, File out, @Nullable Condition<CharSequence> abcNameFilter) throws IOException {
// inputFileParentName = in.getParent().getNameWithoutExtension();
// filter(in.getInputStream(), in.getLength(), out, abcNameFilter);
//
// if (exportAssets != null && !exportAssets.isEmpty()) {
// exportAssets.clear();
// }
//}
private void filter(InputStream inputStream, long inputLength, File outFile, @Nullable Condition<CharSequence> abcNameFilter)
throws IOException {
final boolean onlyABC = outFile.getPath().endsWith(".abc");
final FileOutputStream out = readSourceAndCreateFileOut(inputStream, inputLength, outFile);
channel = out.getChannel();
if (!onlyABC) {
channel.position(PARTIAL_HEADER_LENGTH);
}
try {
if (!onlyABC) {
lastWrittenPosition = 0;
processTags(abcNameFilter);
writeHeader();
}
else {
filterAbcTags(abcNameFilter);
}
}
finally {
channel = null;
out.close();
decoders.clear();
}
}
protected void writeHeader() throws IOException {
final int length = (int)channel.position();
channel.position(0);
buffer.clear();
writePartialHeader(length);
buffer.flip();
channel.write(buffer);
}
@Override
protected int processTag(int type, int length) throws IOException {
switch (type) {
case TagTypes.ShowFrame:
processShowFrame(length);
return -1;
case TagTypes.FileAttributes:
processFileAttributes(length);
return -1;
default:
return super.processTag(type, length);
}
}
private void processShowFrame(int length) throws IOException {
if (decoders.isEmpty()) {
buffer.position(buffer.position() + length);
}
else {
final int limit = buffer.position();
writeDataBeforeTag(length);
mergeDoAbc(true, false);
lastWrittenPosition = limit - 2;
buffer.position(limit + length);
}
}
@Override
protected void processEnd(int length) throws IOException {
buffer.position(lastWrittenPosition);
channel.write(buffer);
}
private void processFileAttributes(int length) {
buffer.put(buffer.position(), (byte)104); // HasMetadata = false
buffer.position(buffer.position() + length);
}
@Override
protected void ensureExportAssetsStorageCreated(int numSymbols) {
if (exportAssets == null) {
exportAssets = new TIntObjectHashMap<>(numSymbols);
}
else {
exportAssets.ensureCapacity(numSymbols);
}
}
@Override
protected void storeExportAsset(int id, int start, int end, boolean mainNameRead) {
if (id == 0) {
return;
}
// -2 - include id length (short)
exportAssets.put(id, new TagPositionInfo(start - 2, end));
}
@Override
protected void processSymbolClass(final int length) throws IOException {
final int tagStartPosition = buffer.position();
writeDataBeforeTag(length);
buffer.position(tagStartPosition);
int numSymbols = analyzeClassAssociatedWithMainTimeline(length);
final boolean hasClassAssociatedWithMainTimeLine = symbolsClassTagLengthWithoutUselessMainClass != -1;
mergeDoAbc(true, hasClassAssociatedWithMainTimeLine);
lastWrittenPosition = tagStartPosition - (length < 63 ? 2 : 6);
buffer.position(lastWrittenPosition);
final boolean hasExportsAssets = exportAssets != null && !exportAssets.isEmpty();
if (hasClassAssociatedWithMainTimeLine || hasExportsAssets) {
if (hasExportsAssets) {
numSymbols += exportAssets.size();
}
if (numSymbols == 0) {
lastWrittenPosition = tagStartPosition + length;
}
else {
int finalSymbolClassTagLength = symbolsClassTagLengthWithoutUselessMainClass;
if (hasExportsAssets) {
final TIntObjectIterator<TagPositionInfo> iterator = exportAssets.iterator();
for (int i = exportAssets.size(); i-- > 0; ) {
iterator.advance();
finalSymbolClassTagLength += iterator.value().length();
}
}
encodeTagHeader(TagTypes.SymbolClass, finalSymbolClassTagLength);
buffer.putShort((short)numSymbols);
buffer.position(lastWrittenPosition);
buffer.limit(sA);
channel.write(buffer);
lastWrittenPosition = sB;
buffer.limit(buffer.capacity());
if (hasExportsAssets) {
final TIntObjectIterator<TagPositionInfo> iterator = exportAssets.iterator();
for (int i = exportAssets.size(); i-- > 0; ) {
iterator.advance();
TagPositionInfo exportAsset = iterator.value();
buffer.position(exportAsset.start);
buffer.limit(exportAsset.end);
channel.write(buffer);
}
exportAssets.clear();
buffer.limit(buffer.capacity());
}
}
symbolsClassTagLengthWithoutUselessMainClass = -1;
}
decoders.clear();
buffer.position(tagStartPosition + length);
}
protected int analyzeClassAssociatedWithMainTimeline(final int length) throws IOException {
int numSymbols = buffer.getShort();
if (numSymbols == 0) {
symbolsClassTagLengthWithoutUselessMainClass = -1;
sA = 0;
sB = 0;
return 0;
}
for (int i = 0; i < numSymbols; i++) {
int id = buffer.getShort();
final int position = buffer.position();
if (id == 0) {
readAbcName(position);
numSymbols--;
symbolsClassTagLengthWithoutUselessMainClass = length - (transientNameString.length() + 1 + 2);
sA = position - 2;
sB = position + transientNameString.length() + 1;
return numSymbols;
}
else {
if (exportAssets != null && !exportAssets.isEmpty()) {
exportAssets.remove(id);
}
buffer.position(position + skipAbcName(position) + 1);
}
}
return numSymbols;
}
@Override
protected void doAbc2(int length) throws IOException {
decoders.add(new Decoder(createBufferWrapper(length), transientNameString.charAt(0) == '_' ? transientNameString.cloneChars() : null));
}
@Override
protected BufferWrapper createBufferWrapper(int length) {
final int off = 4 + transientNameString.length() + 1;
return new BufferWrapper(buffer.array(), buffer.position() + off, length - off);
}
private void mergeDoAbc(boolean asTag, boolean hasClassAssociatedWithMainTimeLine) throws IOException {
final Encoder encoder = new Encoder();
encoder.configure(decoders, hasClassAssociatedWithMainTimeLine ? transientNameString : null);
SwfUtil.mergeDoAbc(decoders, encoder);
encoder.writeDoAbc(channel, asTag);
}
@Override
protected void skipTag(int tagLength) throws IOException {
writeDataBeforeTag(tagLength);
buffer.position(lastWrittenPosition);
}
private void filterAbcTags(Condition<CharSequence> abcNameFilter) throws IOException {
while (true) {
int tagCodeAndLength = buffer.getShort();
int type = tagCodeAndLength >> 6;
int length = tagCodeAndLength & 0x3F;
if (length == 63) {
length = buffer.getInt();
}
switch (type) {
case TagTypes.End:
mergeDoAbc(onlyAbcAsTag, false);
return;
case TagTypes.DoABC2:
readAbcName(buffer.position() + 4);
if (abcNameFilter.value(transientNameString)) {
decoders.add(new Decoder(createBufferWrapper(length)));
}
default:
buffer.position(buffer.position() + length);
}
}
}
}