You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

621 lines
15 KiB
Java

package cokr.xit.base.file;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import cokr.xit.foundation.AbstractComponent;
import cokr.xit.foundation.AbstractEntity;
/**파일 정보
* @author mjkhan
*/
public class FileInfo extends AbstractEntity {
/**파일의 관계 정보
* @author mjkhan
*/
public static class Relation {
private String infoType;
private String infoKey;
private String subType;
/**objs를 대시(-)로 연결하여 infoKey를 반환한다.
* @param objs 아이디로 쓰이는 필드값
* @return infoKey
*/
public static String InfoKey(Object... objs) {
if (isEmpty(objs))
return "";
return Stream.of(objs)
.filter(obj -> !isEmpty(obj))
.map(obj -> obj.toString())
.collect(Collectors.joining("-"));
}
/**관계 정보의 유형을 반환한다.
* @return 관계 정보의 유형
*/
public String getInfoType() {
return infoType;
}
/**관계 정보의 유형을 설정한다.
* @param infoType 관계 정보의 유형
* @return 현재 Relation
*/
public Relation setInfoType(String infoType) {
this.infoType = infoType;
return this;
}
/**관계 정보의 키를 반환한다.
* @return 관계 정보의 키
*/
public String getInfoKey() {
return infoKey;
}
/**관계 정보의 키를 설정한다.
* @param infoKey 관계 정보의 키를
* @return 현재 Relation
*/
public Relation setInfoKey(String infoKey) {
this.infoKey = infoKey;
return this;
}
/**관계 정보의 하위 유형을 반환한다.
* @return 관계 정보의 하위 유형
*/
public String getSubType() {
return subType;
}
/**관계 정보의 하위 유형을 설정한다.
* @param subType 관계 정보의 하위 유형
* @return 현재 Relation
*/
public Relation setSubType(String subType) {
this.subType = subType;
return this;
}
/**관계 정보의 유형, 키, 하위 유형을 fileInfo에 설정한다.
* @param fileInfo FileInfo
*/
public void setInfo(FileInfo fileInfo) {
fileInfo.setInfoType(notEmpty(infoType, "infoType"));
fileInfo.setInfoKey(notEmpty(infoKey, "infoKey"));
if (!isEmpty(subType))
fileInfo.setSubType(subType);
}
}
/**파일 데이터 홀더
* @author mjkhan
*/
public static class DataHolder {
private Relation relation;
private String filename;
private byte[] data;
/**새 DataHolder를 생성한다.
* @param relation 관계 정보
* @param filename 파일 이름
* @param data 파일 데이터
*/
public DataHolder(Relation relation, String filename, byte[] data) {
this.relation = relation;
this.filename = filename;
this.data = data != null ? data : new byte[0];
}
/**새 DataHolder를 생성한다.
* @param relation 관계 정보
* @param filename 파일 이름
* @param data 파일 데이터
*/
public DataHolder(Relation relation, String filename, String data) {
this(relation, filename, !FileInfo.isEmpty(data) ? Base64.getDecoder().decode(data.getBytes()) : null);
}
/**관계 정보를 반환한다.
* @return 관계 정보
*/
public Relation getRelation() {
return relation;
}
/**파일이름을 반환한다.
* @return 파일이름
*/
public String getFilename() {
return filename;
}
/**데이터를 반환한다.
* @return 데이터
*/
public byte[] getData() {
return data;
}
/**데이터의 길이를 반환한다.
* @return 데이터의 길이
*/
public long length() {
return data != null ? data.length : 0;
}
/**데이터가 비어있는지 반환한다.
* @return
* <ul><li>데이터가 비어있으면 true</li>
* <li>그렇지 않으면 false</li>
* </ul>
*/
public boolean isEmpty() {
return length() < 1;
}
}
/**FileInfo 생성자
* @author mjkhan
*/
public static class Factory extends AbstractComponent {
/**주어진 관계 정보 유형과 키, 그리고 파일로 FileInfo 목록을 생성한다.
* @param relation 관계 정보
* @param files 파일
* @return FileInfo 목록
*/
public List<FileInfo> createFileInfos(Relation relation, Iterable<File> files) {
if (isEmpty(files))
return Collections.emptyList();
ArrayList<FileInfo> result = new ArrayList<>();
for (File file: files)
try {
if (file == null) continue;
FileInfo info = new FileInfo();
if (relation != null)
relation.setInfo(info);
String filename = file.getName();
info.setName(filename);
InputStream input = new FileInputStream(file);
String mimeType = URLConnection.guessContentTypeFromName(filename);
if (isEmpty(mimeType))
mimeType = URLConnection.guessContentTypeFromStream(input);
info.setMimeType(mimeType);
info.setInputStream(input);
info.setSize(file.length());
info.setSortOrder(result.size());
result.add(info);
} catch (Exception e) {
throw runtimeException(e);
}
return result;
}
/**주어진 관계 정보 유형과 키, 그리고 파일로 FileInfo 목록을 생성한다.
* @param relation 관계 정보
* @param files 파일
* @return FileInfo 목록
*/
public List<FileInfo> createFileInfos(Relation relation, File... files) {
return createFileInfos(relation, List.of(files));
}
/**주어진 관계 정보 유형과 키, 그리고 파일로 FileInfo을 생성한다.
* @param relation 관계 정보
* @param file 파일
* @return FileInfo
*/
public FileInfo create(Relation relation, File file) {
List<FileInfo> fileInfos = createFileInfos(relation, file);
return !fileInfos.isEmpty() ? fileInfos.get(0) : null;
}
/**파일 DataHolder에서 FileInfo 목록을 생성한다.
* @return FileInfo 목록
*/
public List<FileInfo> createFileInfos(Iterable<DataHolder> dataHolders) {
if (isEmpty(dataHolders))
return Collections.emptyList();
ArrayList<FileInfo> result = new ArrayList<>();
for (DataHolder holder: dataHolders)
try {
if (holder == null) continue;
FileInfo info = new FileInfo();
Relation relation = holder.getRelation();
if (relation != null)
relation.setInfo(info);
String filename = holder.getFilename();
info.setName(filename);
InputStream input = new ByteArrayInputStream(holder.data);
String mimeType = URLConnection.guessContentTypeFromName(filename);
if (isEmpty(mimeType))
mimeType = URLConnection.guessContentTypeFromStream(input);
info.setMimeType(mimeType);
info.setInputStream(input);
info.setSize(holder.length());
info.setSortOrder(result.size());
result.add(info);
} catch (Exception e) {
throw runtimeException(e);
}
return result;
}
/**파일 DataHolder에서 FileInfo 목록을 생성한다.
* @return FileInfo 목록
*/
public List<FileInfo> createFileInfos(DataHolder... dataHolders) {
return createFileInfos(List.of(dataHolders));
}
/**파일 DataHolder에서 FileInfo를 생성한다.
* @return FileInfo
*/
public FileInfo create(DataHolder dataHolder) {
List<FileInfo> list = createFileInfos(dataHolder);
return !list.isEmpty() ? list.get(0) : null;
}
}
private String
id,
infoType,
infoKey,
subType,
subCode,
name,
path,
url,
mimeType,
extension,
etcInfo;
private long size;
private int downloadCount;
private int sortOrder;
private InputStream input;
/**주어진 경로에서 파일 이름을 추출한다.
* @param path 파일 경로
* @return 파일 이름
*/
public static final String name(String path) {
path = path.replace("\\", "/");
return path.substring(path.lastIndexOf("/") + 1);
}
/**주어진 경로에서 디렉토리 이름을 추출한다.
* @param path 파일 경로
* @return 디렉토리 이름
*/
public static final String dir(String path) {
path = path.replace("\\", "/");
int pos = path.lastIndexOf("/");
return pos < 0 ? "" : path.substring(0, pos);
}
/**주어진 파일 이름에서 확장자를 추출한다.
* @param filename 파일 이름
* @return 파일 확장자
*/
public static final String extension(String filename) {
int pos = filename.lastIndexOf(".");
return pos < 0 ? "" : filename.substring(pos + 1);
}
/**id를 반환한다.
* @return id
*/
public String getId() {
return id;
}
/**id를 설정한다.
* @param id id
*/
public void setId(String id) {
this.id = id;
}
/**관계 정보 유형을 반환한다. 프로그램에서 정의
* @return 관계 정보 유형
*/
public String getInfoType() {
return infoType;
}
/**관계 정보 유형을 설정한다. 프로그램에서 정의
* @param infoType 관계 정보 유형
* @return 현재 FileInfo
*/
public FileInfo setInfoType(String infoType) {
this.infoType = infoType;
return this;
}
/**관계 정보 키를 반환한다. 프로그램에서 정의
* @return 관계 정보 키
*/
public String getInfoKey() {
return infoKey;
}
/**관계 정보 키를 설정한다. 프로그램에서 정의
* @param infoKey 관계 정보 키
* @return 현재 FileInfo
*/
public FileInfo setInfoKey(String infoKey) {
this.infoKey = infoKey;
return this;
}
/**관계 정보 기준 하위분류를 반환한다. 프로그램에서 정의
* @return 관계 정보 기준 하위분류
*/
public String getSubType() {
return subType;
}
/**관계 정보 기준 하위분류를 설정한다. 프로그램에서 정의
* @param subType 관계 정보 기준 하위분류
* @return 현재 FileInfo
*/
public FileInfo setSubType(String subType) {
this.subType = subType;
return this;
}
/**관계 정보 기준 하위코드를 반환한다. 프로그램에서 정의.
* @return 관계 정보 기준 하위코드
*/
public String getSubCode() {
return subCode;
}
/**관계 정보 기준 하위코드를 설정한다. 프로그램에서 정의.
* @param subCode 관계 정보 기준 하위코드
* @return 현재 FileInfo
*/
public FileInfo setSubCode(String subCode) {
this.subCode = subCode;
return this;
}
/**파일이름을 반환한다.
* @return 파일이름
*/
public String getName() {
return name;
}
/**파일이름을 설정한다.
* @param name 파일이름
*/
public void setName(String name) {
this.name = name;
}
/**저장경로를 반환한다.
* @return 저장경로
*/
public String getPath() {
return path;
}
/**저장경로를 설정한다.
* @param path 저장경로
* @return 현재 FileInfo
*/
public FileInfo setPath(String path) {
this.path = path;
return this;
}
/**url을 반환한다.
* @return url
*/
public String getUrl() {
return url;
}
/**url을 설정한다.
* @param url url
*/
public void setUrl(String url) {
this.url = url;
}
/**mime type을 반환한다.
* @return mime type
*/
public String getMimeType() {
return ifEmpty(mimeType, () -> mimeType = "application/octet-stream");
}
/**mime type을 설정한다.
* @param mimeType mime type
*/
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
/**확장자를 반환한다.
* @return 확장자
*/
public String getExtension() {
return ifEmpty(extension, () -> extension = extension(name));
}
/**확장자를 설정한다.
* @param extension 확장자
*/
public void setExtension(String extension) {
this.extension = extension;
}
/**기타 추가정보를 반환한다. 프로그램에서 정의.
* @return 추가정보
*/
public String getEtcInfo() {
return etcInfo;
}
/**기타 추가정보를 설정한다. 프로그램에서 정의.
* @param etcInfo 추가정보
*/
public void setEtcInfo(String etcInfo) {
this.etcInfo = etcInfo;
}
/**파일크기를 반환한다.
* @return 파일크기
*/
public long getSize() {
return size;
}
/**파일크기를 설정한다.
* @param size 파일크기
*/
public void setSize(long size) {
this.size = size;
}
/**다운로드 횟수를 반환한다.
* @return 다운로드 횟수
*/
public int getDownloadCount() {
return downloadCount;
}
/**다운로드 횟수를 설정한다.
* @param downloadCount 다운로드 횟수
*/
public void setDownloadCount(int downloadCount) {
if (downloadCount < 0)
throw new IllegalArgumentException("downloadCount < 0");
this.downloadCount = downloadCount;
}
/**정렬순서를 반환한다.
* @return 정렬순서
*/
public int getSortOrder() {
return sortOrder;
}
/**정렬순서를 설정한다.
* @param sortOrder 정렬순서
*/
public void setSortOrder(int sortOrder) {
this.sortOrder = sortOrder;
}
/**FileInfo 경로의 InputStream을 반환한다.
* @return InputStream
*/
public InputStream getInputStream() {
if (input != null) return input;
if (isEmpty(path)) return null;
try {
return new FileInputStream(path);
} catch (Exception e) {
throw runtimeException(e);
}
}
/**InputStream을 설정한다.
* @param input InputStream
* @return FileInfo
*/
public FileInfo setInputStream(InputStream input) {
this.input = input;
return this;
}
/**OutputStream으로 File의 데이터를 저장한다.
* @param out OutputStream
* @return 저장 여부
* <ul><li>저장됐으면 true</li>
* <li>그렇지 않으면 false</li>
* </ul>
*/
public boolean write(OutputStream out) {
if (out == null) return false;
try (
InputStream in = getInputStream();
) {
in.transferTo(out);
return true;
} catch (Exception e) {
throw runtimeException(e);
}
}
/**FileInfo의 경로로 File을 저장한다.
* @return 저장 여부
* <ul><li>저장됐으면 true</li>
* <li>그렇지 않으면 false</li>
* </ul>
*/
public boolean write() {
try (FileOutputStream out = new FileOutputStream(path)) {
return write(out);
} catch(Exception e) {
throw runtimeException(e);
}
}
/**FileInfo의 File을 삭제한다.
* @return 삭제 여부
* <ul><li>삭제됐으면 true</li>
* <li>그렇지 않으면 false</li>
* </ul>
*/
public boolean delete() {
if (isEmpty(path)) return false;
File file = new File(path);
return file.exists() ? file.delete() : false;
}
/**InputStream을 비운다.
* @return FileInfo
*/
public FileInfo close() {
try {
if (input != null)
input.close();
input = null;
return this;
} catch (Exception e) {
throw runtimeException(e);
}
}
@Override
public String toString() {
String path = getPath();
return getName() + (isEmpty(path) ? "" : " -> " + path);
}
}