/*
* Copyright (c) 2017 OBiBa. All rights reserved.
*
* This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.obiba.magma.support;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.util.List;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.obiba.magma.Attribute;
import org.obiba.magma.MagmaRuntimeException;
import org.obiba.magma.Value;
import org.obiba.magma.Variable;
import org.obiba.magma.VariableEntity;
import org.obiba.magma.type.TextType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
/**
*
*/
@SuppressWarnings("UnusedDeclaration")
public class BinaryValueFileHelper {
private static final Logger log = LoggerFactory.getLogger(BinaryValueFileHelper.class);
private BinaryValueFileHelper() {
}
/**
* Read a file into a binary value.
*
* @param parent
* @param path
* @return
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
@edu.umd.cs.findbugs.annotations.SuppressWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
public static byte[] readValue(@Nullable File parent, String path) {
byte[] value = null;
try {
File file = new File(path);
if(!file.isAbsolute() && parent != null) {
file = new File(parent, path);
}
log.debug("Loading binary from: {}", file.getAbsolutePath());
try(FileInputStream fin = new FileInputStream(file)) {
value = new byte[(int) file.length()];
fin.read(value);
}
log.debug("Binary loaded from: {}", file.getAbsolutePath());
} catch(Exception e) {
throw new MagmaRuntimeException("File cannot be read: " + path, e);
}
return value;
}
/**
* Get the value size by the length of the file.
*
* @param parent
* @param path
* @return
*/
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public static long readValueSize(@Nullable File parent, String path) {
try {
File file = new File(path);
if(!file.isAbsolute() && parent != null) {
file = new File(parent, path);
}
return file.length();
} catch(Exception e) {
throw new MagmaRuntimeException("File cannot be read: " + path, e);
}
}
/**
* Remove the file (or files in case of a repeatable variable) for the given variable and entity.
*
* @param parent
* @param variable
* @param entity
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
@edu.umd.cs.findbugs.annotations.SuppressWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
public static void removeValue(File parent, Variable variable, VariableEntity entity) {
final String name = getFileName(variable, entity);
final String extension = getFileExtension(variable);
if(variable.isRepeatable()) {
for(File file : parent.listFiles(new FilenameFilter() {
@Override
public boolean accept(File f, String n) {
return Pattern.matches(name + "-\\d+" + "\\." + extension, n);
}
})) {
file.delete();
}
} else {
removeFileValue(parent, name, extension);
}
}
/**
* @return <code>true</code> if and only if the file or directory is
* successfully deleted; <code>false</code> otherwise
* @see java.io.File#delete()
*/
private static boolean removeFileValue(File parent, String name, String extension) {
File file = new File(parent, name + "." + extension);
return file.delete();
}
/**
* Write binary in file or files in case of a sequence of values.
*
* @param parent
* @param variable
* @param entity
* @param value
* @return
*/
public static Value writeValue(File parent, Variable variable, VariableEntity entity, Value value) {
return writeFileValue(parent, getFileName(variable, entity), getFileExtension(variable), value);
}
/**
* Returns the value representing the file names that were written.
*
* @param parent
* @param name
* @param extension
* @param value
* @return
*/
@SuppressWarnings({ "ResultOfMethodCallIgnored", "PMD.NcssMethodCount" })
@edu.umd.cs.findbugs.annotations.SuppressWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
private static Value writeFileValue(File parent, String name, String extension, Value value) {
if(value.isSequence()) return writeFileValueSequence(parent, name, extension, value);
if(value.isNull()) {
removeFileValue(parent, name, extension);
return TextType.get().nullValue();
}
File file = new File(parent, name + "." + extension);
File tmpFile = new File(parent, file.getName() + ".tmp");
try {
if(!parent.exists()) parent.mkdirs();
tmpFile.createNewFile();
try(FileOutputStream out = new FileOutputStream(tmpFile)) {
out.write((byte[]) value.getValue());
}
if(file.exists()) file.delete();
Files.move(tmpFile, file);
log.debug("File written: {}", file.getAbsolutePath());
} catch(Exception e) {
throw new MagmaRuntimeException("Failed writing file: " + file.getAbsolutePath(), e);
}
return TextType.get().valueOf(file.getName());
}
/**
* Returns the value sequence representing the file names that were written.
*
* @param parent
* @param name
* @param extension
* @param value
* @return
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
@edu.umd.cs.findbugs.annotations.SuppressWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
private static Value writeFileValueSequence(File parent, @SuppressWarnings("TypeMayBeWeakened") final String name,
final String extension, Value value) {
if(value.isNull()) {
// remove all files given a pattern
for(File f : parent.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String n) {
return Pattern.matches(name + "-\\d+\\." + extension, name);
}
})) {
f.delete();
}
return TextType.get().nullSequence();
}
int i = 1;
List<Value> names = Lists.newArrayList();
//noinspection ConstantConditions
for(Value val : value.asSequence().getValue()) {
names.add(writeFileValue(parent, name + "-" + i, extension, val));
i++;
}
// if list was shortened, remove the remaining ones
File remaining = new File(parent, name + "-" + i + "." + extension);
while(remaining.exists()) {
remaining.delete();
i++;
}
return TextType.get().sequenceOf(names);
}
/**
* Get file name from the "filename" or "file-name" variable attribute if any, otherwise use the variable name.
*
* @param variable
* @param entity
* @return
*/
private static String getFileName(Variable variable, VariableEntity entity) {
String prefix = variable.getName();
for(Attribute attr : variable.getAttributes()) {
if("filename".equalsIgnoreCase(attr.getName()) || "file-name".equalsIgnoreCase(attr.getName())) {
String name = variable.getAttributeStringValue(attr.getName());
if(name.length() > 0) {
prefix = name;
break;
}
}
}
return prefix + "-" + entity.getIdentifier();
}
/**
* Get the file extension from the "fileextension" or "file-extension" variable attribute if any.
*
* @param variable
* @return
*/
private static String getFileExtension(@SuppressWarnings("TypeMayBeWeakened") Variable variable) {
String suffix = "bin";
for(Attribute attr : variable.getAttributes()) {
if("fileextension".equalsIgnoreCase(attr.getName()) || "file-extension".equalsIgnoreCase(attr.getName())) {
String extension = variable.getAttributeStringValue(attr.getName());
if(extension.startsWith(".")) {
extension = extension.substring(1);
}
if(extension.length() > 0) {
suffix = extension;
break;
}
}
}
// TODO get file extension from variable mime-type
return suffix;
}
}