package org.elixir_lang.beam;
import com.ericsson.otp.erlang.OtpErlangDecodeException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.indexing.FileContent;
import gnu.trove.THashMap;
import org.elixir_lang.beam.chunk.Atoms;
import org.elixir_lang.beam.chunk.Chunk;
import org.elixir_lang.beam.chunk.Exports;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static org.elixir_lang.beam.chunk.Chunk.TypeID.ATOM;
import static org.elixir_lang.beam.chunk.Chunk.TypeID.EXPT;
import static org.elixir_lang.beam.chunk.Chunk.length;
import static org.elixir_lang.beam.chunk.Chunk.typeID;
/**
* See http://beam-wisdoms.clau.se/en/latest/indepth-beam-file.html
*/
public class Beam {
private static final Logger LOGGER = Logger.getInstance(Beam.class);
/*
* CONSTANTS
*/
private static final String HEADER = "FOR1";
/*
* Fields
*/
private Map<String, Chunk> chunkByTypeID;
/*
* Constructors
*/
private Beam(@NotNull Collection<Chunk> chunkCollection) {
chunkByTypeID = new THashMap<String, Chunk>(chunkCollection.size());
for (Chunk chunk : chunkCollection) {
chunkByTypeID.put(chunk.typeID, chunk);
}
}
/*
* Static Methods
*/
@Nullable
public static Beam from(@NotNull DataInputStream dataInputStream) {
String header;
try {
header = typeID(dataInputStream);
} catch (IOException ioException) {
LOGGER.error("Could not read header from BEAM DataInputStream", ioException);
return null;
}
if (!HEADER.equals(header)) {
return null;
}
try {
length(dataInputStream);
} catch (IOException ioException) {
LOGGER.error("Could not read length from BEAM DataInputStream", ioException);
return null;
}
String section;
try {
section = typeID(dataInputStream);
} catch (IOException ioException) {
LOGGER.error("Could not read section header from BEAM DataInputStream", ioException);
return null;
}
if (!"BEAM".equals(section)) {
LOGGER.error("Section header is not BEAM");
return null;
}
List<Chunk> chunkList = new ArrayList<Chunk>();
int i = 1;
while (true) {
Chunk chunk;
try {
chunk = Chunk.from(dataInputStream);
} catch (IOException ioException) {
LOGGER.error(
"Could not read chunk number " + i + "from BEAM DataInputStream. " +
"Returning truncated Beam object",
ioException
);
break;
}
if (chunk != null) {
chunkList.add(chunk);
} else {
break;
}
}
return new Beam(chunkList);
}
@Nullable
public static Beam from(@NotNull byte[] content) throws IOException, OtpErlangDecodeException {
return from(new DataInputStream(new ByteArrayInputStream(content)));
}
@Nullable
public static Beam from(@NotNull FileContent fileContent) throws IOException, OtpErlangDecodeException {
return from(fileContent.getContent());
}
@Nullable
public static Beam from(@NotNull VirtualFile virtualFile) {
Beam beam = null;
DataInputStream dataInputStream;
try {
dataInputStream = new DataInputStream(virtualFile.getInputStream());
} catch (IOException ioException) {
dataInputStream = null;
}
if (dataInputStream != null) {
beam = Beam.from(dataInputStream);
}
return beam;
}
public static boolean is(@NotNull VirtualFile virtualFile) {
return !virtualFile.isDirectory() && "beam".equals(virtualFile.getExtension());
}
/*
* Instance Methods
*/
@Nullable
public Atoms atoms() {
Atoms atoms = null;
Chunk chunk = chunk(ATOM);
if (chunk != null) {
atoms = Atoms.from(chunk);
}
return atoms;
}
@Nullable
private Chunk chunk(@NotNull String typeID) {
return chunkByTypeID.get(typeID);
}
@Nullable
private Chunk chunk(@NotNull Chunk.TypeID typeID) {
return chunk(typeID.toString());
}
@Nullable
public Exports exports() {
Exports exports = null;
Chunk chunk = chunk(EXPT);
if (chunk != null) {
exports = Exports.from(chunk);
}
return exports;
}
}