/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.drools.compiler.compiler.io.memory;
import org.drools.compiler.commons.jci.readers.ResourceReader;
import org.drools.compiler.commons.jci.stores.ResourceStore;
import org.drools.compiler.compiler.io.File;
import org.drools.compiler.compiler.io.FileSystem;
import org.drools.compiler.compiler.io.Folder;
import org.drools.compiler.compiler.io.Path;
import org.drools.compiler.compiler.io.Resource;
import org.drools.core.util.IoUtils;
import org.drools.core.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.jar.JarInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class MemoryFileSystem
implements
FileSystem,
ResourceReader,
Serializable,
ResourceStore {
private static final Logger log = LoggerFactory.getLogger( MemoryFileSystem.class );
private final MemoryFolder folder;
private final Map<String, Set<Resource>> folders;
private final Map<String, Folder> folderMap;
private final Map<String, byte[]> fileContents;
private Set<String> modifiedFilesSinceLastMark;
public MemoryFileSystem() {
folders = new HashMap<String, Set<Resource>>();
folderMap = new HashMap<String, Folder>();
fileContents = new HashMap<String, byte[]>();
folder = new MemoryFolder( this,
"" );
folders.put( "",
new HashSet<Resource>() );
}
public Folder getRootFolder() {
return folder;
}
public File getFile(Path path) {
return getFile( path.toPortableString() );
}
public Collection<String> getFileNames() {
return fileContents.keySet();
}
public Map<String, byte[]> getMap() {
return this.fileContents;
}
public File getFile(String path) {
path = MemoryFolder.trimLeadingAndTrailing( path );
int lastSlashPos = path.lastIndexOf( '/' );
if ( lastSlashPos >= 0 ) {
Folder folder = getFolder( path.substring( 0,
lastSlashPos ) );
String name = decode( path ).substring( lastSlashPos + 1 );
return new MemoryFile( this,
name,
folder );
} else {
// path is already at root
Folder folder = getRootFolder();
return new MemoryFile( this,
path,
folder );
}
}
public Folder getFolder(Path path) {
return getFolder( path.toPortableString() );
}
public Folder getFolder(String path) {
Folder folder = folderMap.get(path);
if (folder == null) {
folder = new MemoryFolder( this, path );
folderMap.put( path, folder );
}
return folder;
}
public Set< ? extends Resource> getMembers(Folder folder) {
return folders.get( folder.getPath().toPortableString() );
}
public byte[] getFileContents(MemoryFile file) {
return fileContents.get( file.getPath().toPortableString() );
}
public void setFileContents(MemoryFile file,
byte[] contents) throws IOException {
if ( !existsFolder( (MemoryFolder) file.getFolder() ) ) {
createFolder( (MemoryFolder) file.getFolder() );
}
String fileName = file.getPath().toPortableString();
if (modifiedFilesSinceLastMark != null) {
byte[] oldContent = fileContents.get( fileName );
if (oldContent == null || !Arrays.equals(oldContent, contents)) {
modifiedFilesSinceLastMark.add(fileName);
}
}
fileContents.put( fileName,
contents );
folders.get( file.getFolder().getPath().toPortableString() ).add( file );
}
public void mark() {
modifiedFilesSinceLastMark = new HashSet<String>();
}
public Collection<String> getModifiedResourcesSinceLastMark() {
return modifiedFilesSinceLastMark;
}
public boolean existsFolder(MemoryFolder folder) {
return existsFolder( folder.getPath().toPortableString() );
}
public boolean existsFolder(String path) {
if (path == null) {
throw new NullPointerException("Folder path can not be null!");
}
return folders.get(MemoryFolder.trimLeadingAndTrailing(path)) != null;
}
public boolean existsFile(String path) {
if (path == null) {
throw new NullPointerException("File path can not be null!");
}
return fileContents.containsKey(MemoryFolder.trimLeadingAndTrailing(path));
}
public void createFolder(MemoryFolder folder) {
// create current, if it does not exist.
if ( !existsFolder( folder ) ) {
// create parent if it does not exist
if ( !existsFolder( ( MemoryFolder) folder.getParent() ) ) {
createFolder( (MemoryFolder) folder.getParent() );
}
folders.put( folder.getPath().toPortableString(),
new HashSet<Resource>() );
Folder parent = folder.getParent();
folders.get( parent.getPath().toPortableString() ).add( folder );
}
}
public boolean remove(Folder folder) {
if ( folder.exists() ) {
remove( folders.get( folder.getPath().toPortableString() ) );
folders.remove( folder.getPath().toPortableString() );
return true;
} else {
return false;
}
}
public void remove(Set<Resource> members) {
for ( Iterator<Resource> it = members.iterator(); it.hasNext(); ) {
Resource res = it.next();
if ( res instanceof Folder ) {
remove( folders.get( res.getPath().toPortableString() ) );
} else {
String fileName = res.getPath().toPortableString();
fileContents.remove( fileName );
if (modifiedFilesSinceLastMark != null) {
modifiedFilesSinceLastMark.add( fileName );
}
}
it.remove();
}
}
public boolean remove(File file) {
if ( file.exists() ) {
String fileName = file.getPath().toPortableString();
fileContents.remove( fileName );
if (modifiedFilesSinceLastMark != null) {
modifiedFilesSinceLastMark.add( fileName );
}
folders.get( ((MemoryFile) file).getFolder().getPath().toPortableString() ).remove( file );
return true;
} else {
return false;
}
}
public int copyFolder(Folder srcFolder,
MemoryFileSystem trgMfs,
Folder trgFolder,
String... filters) {
return copyFolder( this,
srcFolder,
trgMfs,
trgFolder,
0,
filters );
}
private static int copyFolder(MemoryFileSystem srcMfs,
Folder srcFolder,
MemoryFileSystem trgMfs,
Folder trgFolder,
int count,
String... filters) {
if ( !trgFolder.exists() ) {
trgMfs.getFolder( trgFolder.getPath() ).create();
}
if ( srcFolder != null ) {
for ( Resource rs : srcFolder.getMembers() ) {
if ( rs instanceof Folder ) {
count = copyFolder( srcMfs,
(Folder) rs,
trgMfs,
trgFolder.getFolder( ((Folder) rs).getName() ),
count,
filters );
} else {
MemoryFile trgFile = (MemoryFile) trgFolder.getFile( ((org.drools.compiler.compiler.io.File) rs).getName() );
boolean accept = false;
if ( filters == null || filters.length == 0 ) {
accept = true;
} else {
for ( String filter : filters ) {
if ( trgFile.getName().endsWith( filter ) ) {
accept = true;
break;
}
}
}
if ( accept ) {
try {
trgMfs.setFileContents( trgFile,
srcMfs.getFileContents( (MemoryFile) rs ) );
count++;
} catch ( IOException e ) {
throw new RuntimeException( e );
}
}
}
}
}
return count;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((fileContents == null) ? 0 : fileContents.hashCode());
result = prime * result + ((folder == null) ? 0 : folder.hashCode());
result = prime * result + ((folders == null) ? 0 : folders.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) return true;
if ( obj == null ) return false;
if ( getClass() != obj.getClass() ) return false;
MemoryFileSystem other = (MemoryFileSystem) obj;
if ( fileContents == null ) {
if ( other.fileContents != null ) return false;
} else if ( !fileContents.equals( other.fileContents ) ) return false;
if ( folder == null ) {
if ( other.folder != null ) return false;
} else if ( !folder.equals( other.folder ) ) return false;
if ( folders == null ) {
if ( other.folders != null ) return false;
} else if ( !folders.equals( other.folders ) ) return false;
return true;
}
@Override
public String toString() {
return "MemoryFileSystem [folder=" + folder + ", folders=" + folders + ", fileContents=" + fileContents + "]";
}
public void printFs(PrintStream out) {
printFs( getRootFolder(),
out );
}
public void printFs(Folder f,
PrintStream out) {
for ( Resource rs : f.getMembers() ) {
out.println( rs );
if ( rs instanceof Folder ) {
printFs( (Folder) rs,
out );
} else {
out.println( new String( getFileContents( (MemoryFile) rs ), IoUtils.UTF8_CHARSET ) );
}
}
}
public boolean isAvailable(String pResourceName) {
return existsFile( pResourceName );
}
public byte[] getBytes(String pResourceName) {
return getFileContents((MemoryFile) getFile(pResourceName));
}
public void write(String pResourceName,
byte[] pResourceData) {
write( pResourceName,
pResourceData,
false );
}
public void write(String pResourceName,
byte[] pResourceData,
boolean createFolder) {
if (pResourceData.length == 0 && pResourceName.endsWith( "/" )) {
// avoid to create files for empty folders
return;
}
MemoryFile memoryFile = (MemoryFile) getFile( pResourceName );
if ( createFolder ) {
String folderPath = memoryFile.getFolder().getPath().toPortableString();
if ( !existsFolder( folderPath ) ) {
memoryFile.getFolder().create();
}
}
try {
setFileContents( memoryFile,
pResourceData );
} catch ( IOException e ) {
throw new RuntimeException( e );
}
}
public byte[] read(String pResourceName) {
return getBytes( pResourceName );
}
public void remove(String pResourceName) {
remove(getFile(pResourceName));
}
public byte[] writeAsBytes() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
zip( baos );
return baos.toByteArray();
}
public java.io.File writeAsJar(java.io.File folder,
String jarName) {
try {
java.io.File jarFile = new java.io.File( folder,
jarName + ".jar" );
System.out.println( jarFile );
zip( new FileOutputStream( jarFile ) );
return jarFile;
} catch ( IOException e ) {
throw new RuntimeException( e );
}
}
private void zip(OutputStream outputStream) {
ZipOutputStream out = null;
try {
out = new ZipOutputStream( outputStream );
writeJarEntries( getRootFolder(),
out );
out.close();
} catch ( IOException e ) {
throw new RuntimeException( e );
} finally {
try {
if (out != null) {
out.close();
}
} catch ( IOException e ) {
log.error(e.getMessage(), e);
}
}
}
public void writeAsFs(java.io.File file) {
file.mkdir();
writeAsFs(this.getRootFolder(), file);
}
public void writeAsFs(Folder f,
java.io.File file1) {
for ( Resource rs : f.getMembers() ) {
if ( rs instanceof Folder ) {
java.io.File file2 = new java.io.File( file1, ((Folder) rs).getName());
file2.mkdir();
writeAsFs( (Folder) rs, file2 );
} else {
byte[] bytes = getFileContents( (MemoryFile) rs );
try {
IoUtils.write(new java.io.File(file1, ((File) rs).getName()), bytes);
} catch ( IOException e ) {
throw new RuntimeException("Unable to write project to file system\n", e);
}
}
}
}
private void writeJarEntries(Folder f,
ZipOutputStream out) throws IOException {
for ( Resource rs : f.getMembers() ) {
String rname = rs.getPath().toPortableString();
if ( rs instanceof Folder ) {
rname = rname.endsWith("/") ? rname : rname + "/"; // a folder name must end with / according to ZIP spec
ZipEntry entry = new ZipEntry( rname );
out.putNextEntry( entry );
writeJarEntries( (Folder) rs,
out );
} else {
ZipEntry entry = new ZipEntry( rname );
out.putNextEntry( entry );
byte[] contents = getFileContents( (MemoryFile) rs );
if (contents == null) {
IOException e = new IOException("No content found for: " + rs);
log.error(e.getMessage(), e);
throw e;
}
out.write( contents );
out.closeEntry();
}
}
}
public static MemoryFileSystem readFromJar(java.io.File jarFile) {
MemoryFileSystem mfs = new MemoryFileSystem();
ZipFile zipFile = null;
try {
zipFile = new ZipFile( jarFile );
Enumeration< ? extends ZipEntry> entries = zipFile.entries();
while ( entries.hasMoreElements() ) {
ZipEntry entry = entries.nextElement();
int separator = entry.getName().lastIndexOf( '/' );
String path = separator > 0 ? entry.getName().substring( 0, separator ) : "";
String name = entry.getName().substring( separator + 1 );
Folder folder = mfs.getFolder( path );
folder.create();
File file = folder.getFile( name );
file.create( zipFile.getInputStream( entry ) );
}
} catch ( IOException e ) {
throw new RuntimeException( e );
} finally {
if ( zipFile != null ) {
try {
zipFile.close();
} catch ( IOException e ) {
log.error(e.getMessage(), e);
}
}
}
return mfs;
}
public static MemoryFileSystem readFromJar(byte[] jarFile) {
return readFromJar( new ByteArrayInputStream( jarFile ) );
}
public static MemoryFileSystem readFromJar(InputStream jarFile) {
MemoryFileSystem mfs = new MemoryFileSystem();
JarInputStream zipFile = null;
try {
zipFile = new JarInputStream( jarFile );
ZipEntry entry;
while ( (entry = zipFile.getNextEntry()) != null ) {
// entry.getSize() is not accurate according to documentation, so have to read bytes until -1 is found
ByteArrayOutputStream content = new ByteArrayOutputStream();
int b;
while( (b = zipFile.read()) != -1 ) {
content.write( b );
}
mfs.write( entry.getName(), content.toByteArray(), true );
}
} catch ( IOException e ) {
throw new RuntimeException( e );
} finally {
if ( zipFile != null ) {
try {
zipFile.close();
} catch ( IOException e ) {
log.error(e.getMessage(), e);
}
}
}
return mfs;
}
public String findPomProperties() {
for( Entry<String, byte[]> content : fileContents.entrySet() ) {
if ( content.getKey().endsWith( "pom.properties" ) && content.getKey().startsWith( "META-INF/maven/" ) ) {
ByteArrayInputStream byteArrayIs = new ByteArrayInputStream( content.getValue() );
return StringUtils.readFileAsString( new InputStreamReader( byteArrayIs, IoUtils.UTF8_CHARSET ) );
}
}
return null;
}
public MemoryFileSystem clone() {
MemoryFileSystem clone = new MemoryFileSystem();
for (Map.Entry<String, byte[]> entry : fileContents.entrySet()) {
clone.write(entry.getKey(), entry.getValue());
}
return clone;
}
private String decode( final String path ) {
try {
return URLDecoder.decode( path, "UTF-8" );
} catch ( UnsupportedEncodingException e ) {
return path;
}
}
}