/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.repository.local;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.rapidminer.operator.IOObject;
import com.rapidminer.operator.Operator;
import com.rapidminer.repository.BlobEntry;
import com.rapidminer.repository.DataEntry;
import com.rapidminer.repository.DateEntry;
import com.rapidminer.repository.Folder;
import com.rapidminer.repository.IOObjectEntry;
import com.rapidminer.repository.ProcessEntry;
import com.rapidminer.repository.RepositoryException;
import com.rapidminer.repository.RepositoryLocation;
import com.rapidminer.repository.RepositoryTools;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.ProgressListener;
import com.rapidminer.tools.Tools;
/**
* @author Simon Fischer
*/
public class SimpleFolder extends SimpleEntry implements Folder, DateEntry {
private List<DataEntry> data;
private List<Folder> folders;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
SimpleFolder(String name, SimpleFolder parent, LocalRepository repository) {
super(name, parent, repository);
}
protected void mkdir() throws RepositoryException {
File file = getFile();
if (!file.exists()) {
if (!file.mkdirs()) {
throw new RepositoryException("Cannot create repository folder at '" + file + "'.");
}
}
}
@Override
protected void handleRename(String newName) throws RepositoryException {
renameFile(getFile(), newName);
}
@Override
protected void handleMove(Folder newParent, String newName) throws RepositoryException {
moveFile(getFile(), ((SimpleFolder) newParent).getFile(), newName, "");
}
protected File getFile() {
return new File(((SimpleFolder) getContainingFolder()).getFile(), getName());
}
@Override
public List<DataEntry> getDataEntries() throws RepositoryException {
acquireReadLock();
try {
if (isLoaded()) {
return Collections.unmodifiableList(data);
}
} finally {
releaseReadLock();
}
acquireWriteLock();
try {
ensureLoaded();
return Collections.unmodifiableList(data);
} finally {
releaseWriteLock();
}
}
@Override
public List<Folder> getSubfolders() throws RepositoryException {
acquireReadLock();
try {
if (isLoaded()) {
return Collections.unmodifiableList(folders);
}
} finally {
releaseReadLock();
}
acquireWriteLock();
try {
ensureLoaded();
return Collections.unmodifiableList(folders);
} finally {
releaseWriteLock();
}
}
private boolean isLoaded() {
return data != null && folders != null;
}
/**
* Makes sure the corresponding content is loaded. This method will perform write operations,
* you need to acquire the write lock before calling it.
*/
private void ensureLoaded() throws RepositoryException {
if (isLoaded()) {
return;
}
data = new ArrayList<DataEntry>();
folders = new ArrayList<Folder>();
File fileFolder = getFile();
if (fileFolder != null && fileFolder.exists()) {
File[] listFiles = fileFolder.listFiles();
for (File file : listFiles) {
if (file.isHidden()) {
continue;
}
if (file.isDirectory()) {
folders.add(new SimpleFolder(file.getName(), this, getRepository()));
} else if (file.getName().endsWith(".ioo")) {
data.add(new SimpleIOObjectEntry(file.getName().substring(0, file.getName().length() - 4), this,
getRepository()));
} else if (file.getName().endsWith(".rmp")) {
data.add(new SimpleProcessEntry(file.getName().substring(0, file.getName().length() - 4), this,
getRepository()));
} else if (file.getName().endsWith(".blob")) {
data.add(new SimpleBlobEntry(file.getName().substring(0, file.getName().length() - 5), this,
getRepository()));
}
Collections.sort(data, RepositoryTools.SIMPLE_NAME_COMPARATOR);
Collections.sort(folders, RepositoryTools.SIMPLE_NAME_COMPARATOR);
}
}
}
@Override
public IOObjectEntry createIOObjectEntry(String name, IOObject ioobject, Operator callingOperator, ProgressListener l)
throws RepositoryException {
// check for possible invalid name
if (!RepositoryLocation.isNameValid(name)) {
throw new RepositoryException(
I18N.getMessage(I18N.getErrorBundle(), "repository.illegal_entry_name", name, getLocation()));
}
IOObjectEntry entry = new SimpleIOObjectEntry(name, this, getRepository());
acquireWriteLock();
try {
ensureLoaded();
data.add(entry);
} finally {
releaseWriteLock();
}
if (ioobject != null) {
entry.storeData(ioobject, null, l);
}
getRepository().fireEntryAdded(entry, this);
return entry;
}
@Override
public Folder createFolder(String name) throws RepositoryException {
// check for possible invalid name
if (!RepositoryLocation.isNameValid(name)) {
throw new RepositoryException(
I18N.getMessage(I18N.getErrorBundle(), "repository.illegal_entry_name", name, getLocation()));
}
SimpleFolder newFolder = new SimpleFolder(name, this, getRepository());
acquireWriteLock();
try {
ensureLoaded();
for (Folder folder : folders) {
// folder with the same name (no matter if they have different capitalization) must
// not
// be created
if (folder.getName().toLowerCase(Locale.ENGLISH).equals(name.toLowerCase(Locale.ENGLISH))) {
throw new RepositoryException(
I18N.getMessage(I18N.getErrorBundle(), "repository.repository_folder_already_exists", name));
}
}
for (DataEntry entry : data) {
if (entry.getName().equals(name)) {
throw new RepositoryException(I18N.getMessage(I18N.getErrorBundle(),
"repository.repository_entry_with_same_name_already_exists", name));
}
}
newFolder.mkdir();
folders.add(newFolder);
} finally {
releaseWriteLock();
}
getRepository().fireEntryAdded(newFolder, this);
return newFolder;
}
@Override
public String getDescription() {
return "Folder '" + getName() + "'";
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public String getType() {
return Folder.TYPE_NAME;
}
@Override
public void refresh() throws RepositoryException {
acquireWriteLock();
try {
data = null;
folders = null;
} finally {
releaseWriteLock();
}
getRepository().fireRefreshed(this);
}
@Override
public boolean containsEntry(String name) throws RepositoryException {
acquireReadLock();
try {
if (isLoaded()) {
return containsEntryNotThreadSafe(name);
}
} finally {
releaseReadLock();
}
acquireWriteLock();
try {
ensureLoaded();
return containsEntryNotThreadSafe(name);
} finally {
releaseWriteLock();
}
}
private boolean containsEntryNotThreadSafe(String name) {
for (Folder folder : folders) {
if (folder.getName().equals(name)) {
return true;
}
}
for (DataEntry entry : data) {
if (entry.getName().equals(name)) {
return true;
}
}
return false;
}
@Override
public void delete() throws RepositoryException {
if (!Tools.delete(getFile())) {
throw new RepositoryException("Cannot delete directory");
} else {
super.delete();
}
}
void removeChild(SimpleEntry child) throws RepositoryException {
int index;
acquireWriteLock();
try {
ensureLoaded();
if (child instanceof SimpleFolder) {
index = folders.indexOf(child);
folders.remove(child);
} else {
index = data.indexOf(child) + folders.size();
data.remove(child);
}
} finally {
releaseWriteLock();
}
getRepository().fireEntryRemoved(child, this, index);
}
void addChild(SimpleEntry child) throws RepositoryException {
acquireWriteLock();
try {
ensureLoaded();
if (child instanceof SimpleFolder) {
folders.add((Folder) child);
} else {
data.add((DataEntry) child);
}
} finally {
releaseWriteLock();
}
getRepository().fireEntryAdded(child, this);
}
@Override
public ProcessEntry createProcessEntry(String name, String processXML) throws RepositoryException {
// check for possible invalid name
if (!RepositoryLocation.isNameValid(name)) {
throw new RepositoryException(
I18N.getMessage(I18N.getErrorBundle(), "repository.illegal_entry_name", name, getLocation()));
}
SimpleProcessEntry entry = null;
acquireWriteLock();
try {
ensureLoaded();
entry = new SimpleProcessEntry(name, this, getRepository());
data.add(entry);
try {
entry.storeXML(processXML);
} catch (RepositoryException e) {
data.remove(entry);
throw e;
}
} finally {
releaseWriteLock();
}
if (entry != null) {
getRepository().fireEntryAdded(entry, this);
}
return entry;
}
@Override
public BlobEntry createBlobEntry(String name) throws RepositoryException {
// check for possible invalid name
if (!RepositoryLocation.isNameValid(name)) {
throw new RepositoryException(
I18N.getMessage(I18N.getErrorBundle(), "repository.illegal_entry_name", name, getLocation()));
}
BlobEntry entry = null;
acquireWriteLock();
try {
ensureLoaded();
entry = new SimpleBlobEntry(name, this, getRepository());
data.add(entry);
} finally {
releaseWriteLock();
}
if (entry != null) {
getRepository().fireEntryAdded(entry, this);
}
return entry;
}
@Override
public boolean canRefreshChild(String childName) throws RepositoryException {
// check existence of properties file
return new File(getFile(), childName + SimpleEntry.PROPERTIES_SUFFIX).exists();
}
private void acquireReadLock() throws RepositoryException {
try {
readLock.lock();
} catch (RuntimeException e) {
throw new RepositoryException("Could not get read lock", e);
}
}
private void releaseReadLock() throws RepositoryException {
try {
readLock.unlock();
} catch (RuntimeException e) {
throw new RepositoryException("Could not release read lock", e);
}
}
private void acquireWriteLock() throws RepositoryException {
try {
writeLock.lock();
} catch (RuntimeException e) {
throw new RepositoryException("Could not get write lock", e);
}
}
private void releaseWriteLock() throws RepositoryException {
try {
writeLock.unlock();
} catch (RuntimeException e) {
throw new RepositoryException("Could not release write lock", e);
}
}
/**
* @since 7.4
*/
@Override
public long getDate() {
return getFile().lastModified();
}
}