package com.redhat.ceylon.model.loader.impl.reflect;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import com.redhat.ceylon.common.FileUtil;
import com.redhat.ceylon.model.cmr.ArtifactResult;
import com.redhat.ceylon.model.cmr.PathFilter;
import com.redhat.ceylon.model.loader.ContentAwareArtifactResult;
import com.redhat.ceylon.model.typechecker.model.Module;
public class CachedTOCJars {
/**
* Jar file where we cache the TOC
*/
static class CachedTOCJar {
ArtifactResult artifact;
// stores class file names with slashes
Set<String> contents = new HashSet<String>();
// stores package paths with slashes but not last one
Set<String> packages = new HashSet<String>();
// not not attempt to load contents from this jar, just its TOC
boolean skipContents;
CachedTOCJar(ArtifactResult artifact, boolean skipContents){
this.artifact = artifact;
this.skipContents = skipContents;
if (artifact instanceof ContentAwareArtifactResult) {
packages.addAll(((ContentAwareArtifactResult) artifact).getPackages());
contents.addAll(((ContentAwareArtifactResult) artifact).getEntries());
} else {
if (artifact.artifact() != null) {
try {
ZipFile zf = new ZipFile(artifact.artifact());
try{
Enumeration<? extends ZipEntry> entries = zf.entries();
while(entries.hasMoreElements()){
ZipEntry entry = entries.nextElement();
// only cache class files
if(!entry.isDirectory() && accept(entry.getName())){
packages.add(getPackageName(entry.getName()));
contents.add(entry.getName());
}
}
}finally{
zf.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
private boolean accept(String path) {
PathFilter filter = artifact.filter();
return filter == null || filter.accept(path);
}
private String getPackageName(String name) {
int lastSlash = name.lastIndexOf('/');
if(lastSlash == -1)
return "";
return name.substring(0, lastSlash);
}
boolean containsFile(String path){
return contents.contains(path);
}
boolean containsPackage(String path) {
return packages.contains(path);
}
byte[] getContents(String path){
if (artifact instanceof ContentAwareArtifactResult) {
return ((ContentAwareArtifactResult) artifact).getContents(path);
}
File jar = artifact.artifact();
if (jar != null) {
try {
ZipFile zf = new ZipFile(jar);
try{
ZipEntry entry = zf.getEntry(path);
if(entry != null)
return loadFile(zf.getInputStream(entry), (int)entry.getSize());
}finally{
zf.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
throw new RuntimeException("Missing entry: "+path+" in jar file: "+ jar.getPath());
}
throw new RuntimeException("No file associated with artifact : " + artifact.toString());
}
URI getContentUri(String path){
if (artifact instanceof ContentAwareArtifactResult) {
return ((ContentAwareArtifactResult) artifact).getContentUri(path);
}
File jar = artifact.artifact();
if (jar != null) {
try {
ZipFile zf = new ZipFile(jar);
try{
ZipEntry entry = zf.getEntry(path);
if (entry != null) {
String uripath = FileUtil.absoluteFile(jar).toURI().getSchemeSpecificPart();
return new URI("classpath:" + uripath + "!" + entry.getName());
}
}finally{
zf.close();
}
} catch (IOException | URISyntaxException e) {
throw new RuntimeException(e);
}
throw new RuntimeException("Missing entry: "+path+" in jar file: "+ jar.getPath());
}
throw new RuntimeException("No file associated with artifact : " + artifact.toString());
}
private byte[] loadFile(InputStream inputStream, int size) throws IOException {
byte[] buf = new byte[size];
try{
int read;
int offset = 0;
while(offset != size && (read = inputStream.read(buf, offset, size - offset)) >= 0){
offset += read;
}
return buf;
}finally {
inputStream.close();
}
}
private List<String> getFileNames(String path){
if (artifact instanceof ContentAwareArtifactResult) {
return ((ContentAwareArtifactResult) artifact).getFileNames(path);
}
File jar = artifact.artifact();
if (jar != null) {
try {
// add a trailing / to only list members
boolean emptyPackage = path.isEmpty();
// only used for non-empty packages
path += "/";
ZipFile zf = new ZipFile(jar);
try{
Enumeration<? extends ZipEntry> entries = zf.entries();
List<String> ret = new ArrayList<String>();
while(entries.hasMoreElements()){
ZipEntry entry = entries.nextElement();
String name = entry.getName();
// only cache class files
if(!entry.isDirectory() && accept(name)){
String part = null;
if(!emptyPackage && name.startsWith(path)){
// keep only the part after the package name
part = name.substring(path.length());
}else if(emptyPackage){
// keep it all, we'll filter later those in subfolders
part = name;
}
// only keep those not in subfolders
if(part != null && part.indexOf('/') == -1)
ret.add(name);
}
}
return ret;
}finally{
zf.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException("No file associated with artifact : " + artifact.toString());
}
}
@Override
public String toString(){
return "CachedTOCJar[jar="+artifact+"; contents="+contents+"; packages="+packages+"]";
}
}
private Map<Module, CachedTOCJar> jars = new HashMap<Module, CachedTOCJar>();
public void addJar(ArtifactResult artifact, Module module) {
addJar(artifact, module, false);
}
public void addJar(ArtifactResult artifact, Module module, boolean skipContents) {
// skip duplicates
if(jars.containsKey(module))
return;
jars.put(module, new CachedTOCJar(artifact, skipContents));
}
public boolean packageExists(Module module, String name) {
String path = name.replace('.', '/');
CachedTOCJar jar = jars.get(module);
return jar != null && jar.containsPackage(path);
}
public List<String> getPackageList(Module module, String name) {
String path = name.replace('.', '/');
CachedTOCJar jar = jars.get(module);
return jar != null && jar.containsPackage(path) ?
jar.getFileNames(path) : Collections.<String>emptyList();
}
public byte[] getContents(String path) {
for(CachedTOCJar jar : jars.values()){
if(!jar.skipContents && jar.containsFile(path)){
return jar.getContents(path);
}
}
return null;
}
public URI getContentUri(String path) {
for(CachedTOCJar jar : jars.values()){
if(!jar.skipContents && jar.containsFile(path)){
return jar.getContentUri(path);
}
}
return null;
}
public byte[] getContents(Module module, String path) {
CachedTOCJar jar = jars.get(module);
if(jar != null && !jar.skipContents && jar.containsFile(path)){
return jar.getContents(path);
}
return null;
}
public URI getContentUri(Module module, String path) {
CachedTOCJar jar = jars.get(module);
if(jar != null && !jar.skipContents && jar.containsFile(path)){
return jar.getContentUri(path);
}
return null;
}
@Override
public String toString(){
return "CachedTOCJars[jars="+jars+"]";
}
}