/*
* Syncany, www.syncany.org
* Copyright (C) 2011-2015 Philipp C. Heckel <philipp.heckel@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* 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.syncany.plugins.transfer.files;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.syncany.plugins.transfer.StorageException;
import org.syncany.plugins.transfer.TransferManager;
import org.syncany.util.StringUtil;
import com.google.common.collect.Maps;
/**
* A remote file represents a file object on a remote storage. Its purpose is to
* identify a file and allow {@link TransferManager}s to upload/download local files.
*
* <p>Transfer manager operations take either <tt>RemoteFile</tt> instances, or classes
* that extend this class. Depending on the type of the sub-class, they might store the
* files at a different location or in a different format to optimize performance.
*
* <p>Remote files can be extended with {@link RemoteFileAttributes} in certain situations,
* e.g. to add additional information about the sub-path. The attributes can be added set
* and read via {@link #setAttributes(RemoteFileAttributes)} and {@link #getAttributes(Class)}.
*
* <p><b>Important:</b> Sub-classes must offer a
* {@link RemoteFile#RemoteFile(String) one-parameter constructor} that takes a
* <tt>String</tt> argument. This constructor is required by the {@link RemoteFileFactory}.
*
* @author Philipp C. Heckel <philipp.heckel@gmail.com>
*/
public abstract class RemoteFile {
private static final Logger logger = Logger.getLogger(RemoteFile.class.getSimpleName());
private static final String REMOTE_FILE_PACKAGE = RemoteFile.class.getPackage().getName();
private static final String REMOTE_FILE_SUFFIX = RemoteFile.class.getSimpleName();
private String name;
private Map<Class<? extends RemoteFileAttributes>, RemoteFileAttributes> attributes;
/**
* Creates a new remote file by its name. The name is used by {@link TransferManager}s
* to identify a file on the remote storage.
*
* <p>The constructor parses and validates the given name using the
* {@link #validateName(String) validateName()} method. While <tt>RemoteFile</tt> has no name
* pattern (and never throws an exception), sub-classes might.
*
* <p><b>Important:</b> Sub-classes must also implement a one-parameter constructor that takes a
* <tt>String</tt> argument. This constructor is required by the {@link RemoteFileFactory}.
*
* @param name The name of the file (as it is identified by Syncany)
* @throws StorageException If the name does not match the name pattern defined by the class.<br />
* <b>Note:</b> <tt>RemoteFile</tt> does never throw this exceptions, however, subclasses might.
*/
public RemoteFile(String name) throws StorageException {
this.name = validateName(name);
this.attributes = Maps.newHashMap();
}
/**
* Returns the name of the file (as it is identified by Syncany)
*/
public final String getName() {
return name;
}
/**
* Sets remote file attributes to this remote file class. Attributes
* can extend the parameters of this class without actually having to extend it.
*/
public final <T extends RemoteFileAttributes> void setAttributes(T remoteFileAttributes) {
attributes.put(remoteFileAttributes.getClass(), remoteFileAttributes);
}
/**
* Returns a list of attributes for a given file,
* or null if there is no attribute with the given class.
*/
@SuppressWarnings("unchecked")
public final <T extends RemoteFileAttributes> T getAttributes(Class<T> remoteFileAttributesClass) {
return (T) attributes.get(remoteFileAttributesClass);
}
/**
* Parses the name of the file and validates it against the classes name pattern. While
* <tt>RemoteFile</tt> has no name pattern (and never throws an exception), sub-classes might by
* overriding this method.
*
* @param name The name of the file (as it is identified by Syncany)
* @return Returns a (potentially changed) name, after validating the name
* @throws StorageException If the name does not match the name pattern defined by the class.<br />
* <b>Note:</b> <tt>RemoteFile</tt> does never throw this exceptions, however, subclasses might.
*/
protected String validateName(String name) throws StorageException {
return name;
}
/**
* Creates a remote file based on a name and a class name.
*
* <p>The name must match the corresponding name pattern, and the class name
* can either be <tt>RemoteFile</tt>, or a sub-class thereof.
*
* @param name The name of the remote file
* @param remoteFileClass Class name of the object to instantiate, <tt>RemoteFile</tt> or a sub-class thereof
* @return Returns a new object of the given class
*/
public static <T extends RemoteFile> T createRemoteFile(String name, Class<T> remoteFileClass) throws StorageException {
try {
return remoteFileClass.getConstructor(String.class).newInstance(name);
}
catch (Exception e) {
throw new StorageException(e);
}
}
/**
* Creates a remote file based on a name and derives the class name using the
* file name.
*
* <p>The name must match the corresponding name pattern (nameprefix-...), and
* the derived class can either be <tt>RemoteFile</tt>, or a sub-class thereof.
*
* @param name The name of the remote file
* @return Returns a new object of the given class
*/
@SuppressWarnings("unchecked")
public static <T extends RemoteFile> T createRemoteFile(String name) throws StorageException {
String prefix = name.contains("-") ? name.substring(0, name.indexOf('-')) : name;
String camelCasePrefix = StringUtil.toCamelCase(prefix);
try {
Class<T> remoteFileClass = (Class<T>) Class.forName(REMOTE_FILE_PACKAGE + "." + camelCasePrefix + REMOTE_FILE_SUFFIX);
return createRemoteFile(name, remoteFileClass);
}
catch (ClassNotFoundException | StorageException e) {
logger.log(Level.INFO, "Invalid filename for remote file " + name);
throw new StorageException("Invalid filename for remote file " + name);
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !(obj instanceof RemoteFile)) {
return false;
}
RemoteFile other = (RemoteFile) obj;
if (name == null) {
if (other.name != null) {
return false;
}
}
else if (!name.equals(other.name)) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public String toString() {
return RemoteFile.class.getSimpleName() + "[name=" + name + "]";
}
}