001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.plugins.transfer.files; 019 020import java.util.Map; 021import java.util.logging.Level; 022import java.util.logging.Logger; 023 024import org.syncany.plugins.transfer.StorageException; 025import org.syncany.plugins.transfer.TransferManager; 026import org.syncany.util.StringUtil; 027 028import com.google.common.collect.Maps; 029 030/** 031 * A remote file represents a file object on a remote storage. Its purpose is to 032 * identify a file and allow {@link TransferManager}s to upload/download local files. 033 * 034 * <p>Transfer manager operations take either <code>RemoteFile</code> instances, or classes 035 * that extend this class. Depending on the type of the sub-class, they might store the 036 * files at a different location or in a different format to optimize performance. 037 * 038 * <p>Remote files can be extended with {@link RemoteFileAttributes} in certain situations, 039 * e.g. to add additional information about the sub-path. The attributes can be added set 040 * and read via {@link #setAttributes(RemoteFileAttributes)} and {@link #getAttributes(Class)}. 041 * 042 * <p><b>Important:</b> Sub-classes must offer a 043 * {@link RemoteFile#RemoteFile(String) one-parameter constructor} that takes a 044 * <code>String</code> argument. This constructor is required by the {@link RemoteFile}. 045 * 046 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 047 */ 048public abstract class RemoteFile { 049 private static final Logger logger = Logger.getLogger(RemoteFile.class.getSimpleName()); 050 051 private static final String REMOTE_FILE_PACKAGE = RemoteFile.class.getPackage().getName(); 052 private static final String REMOTE_FILE_SUFFIX = RemoteFile.class.getSimpleName(); 053 054 private String name; 055 private Map<Class<? extends RemoteFileAttributes>, RemoteFileAttributes> attributes; 056 057 /** 058 * Creates a new remote file by its name. The name is used by {@link TransferManager}s 059 * to identify a file on the remote storage. 060 * 061 * <p>The constructor parses and validates the given name using the 062 * {@link #validateName(String) validateName()} method. While <code>RemoteFile</code> has no name 063 * pattern (and never throws an exception), sub-classes might. 064 * 065 * <p><b>Important:</b> Sub-classes must also implement a one-parameter constructor that takes a 066 * <code>String</code> argument. This constructor is required by the {@link RemoteFile}. 067 * 068 * @param name The name of the file (as it is identified by Syncany) 069 * @throws StorageException If the name does not match the name pattern defined by the class.<br> 070 * <b>Note:</b> <code>RemoteFile</code> does never throw this exceptions, however, subclasses might. 071 */ 072 public RemoteFile(String name) throws StorageException { 073 this.name = validateName(name); 074 this.attributes = Maps.newHashMap(); 075 } 076 077 /** 078 * Returns the name of the file (as it is identified by Syncany) 079 */ 080 public final String getName() { 081 return name; 082 } 083 084 /** 085 * Sets remote file attributes to this remote file class. Attributes 086 * can extend the parameters of this class without actually having to extend it. 087 */ 088 public final <T extends RemoteFileAttributes> void setAttributes(T remoteFileAttributes) { 089 attributes.put(remoteFileAttributes.getClass(), remoteFileAttributes); 090 } 091 092 /** 093 * Returns a list of attributes for a given file, 094 * or null if there is no attribute with the given class. 095 */ 096 @SuppressWarnings("unchecked") 097 public final <T extends RemoteFileAttributes> T getAttributes(Class<T> remoteFileAttributesClass) { 098 return (T) attributes.get(remoteFileAttributesClass); 099 } 100 101 /** 102 * Parses the name of the file and validates it against the classes name pattern. While 103 * <code>RemoteFile</code> has no name pattern (and never throws an exception), sub-classes might by 104 * overriding this method. 105 * 106 * @param name The name of the file (as it is identified by Syncany) 107 * @return Returns a (potentially changed) name, after validating the name 108 * @throws StorageException If the name does not match the name pattern defined by the class.<br> 109 * <b>Note:</b> <code>RemoteFile</code> does never throw this exceptions, however, subclasses might. 110 */ 111 protected String validateName(String name) throws StorageException { 112 return name; 113 } 114 115 /** 116 * Creates a remote file based on a name and a class name. 117 * 118 * <p>The name must match the corresponding name pattern, and the class name 119 * can either be <code>RemoteFile</code>, or a sub-class thereof. 120 * 121 * @param name The name of the remote file 122 * @param remoteFileClass Class name of the object to instantiate, <code>RemoteFile</code> or a sub-class thereof 123 * @return Returns a new object of the given class 124 */ 125 public static <T extends RemoteFile> T createRemoteFile(String name, Class<T> remoteFileClass) throws StorageException { 126 try { 127 return remoteFileClass.getConstructor(String.class).newInstance(name); 128 } 129 catch (Exception e) { 130 throw new StorageException(e); 131 } 132 } 133 134 /** 135 * Creates a remote file based on a name and derives the class name using the 136 * file name. 137 * 138 * <p>The name must match the corresponding name pattern (nameprefix-...), and 139 * the derived class can either be <code>RemoteFile</code>, or a sub-class thereof. 140 * 141 * @param name The name of the remote file 142 * @return Returns a new object of the given class 143 */ 144 @SuppressWarnings("unchecked") 145 public static <T extends RemoteFile> T createRemoteFile(String name) throws StorageException { 146 String prefix = name.contains("-") ? name.substring(0, name.indexOf('-')) : name; 147 String camelCasePrefix = StringUtil.toCamelCase(prefix); 148 149 try { 150 Class<T> remoteFileClass = (Class<T>) Class.forName(REMOTE_FILE_PACKAGE + "." + camelCasePrefix + REMOTE_FILE_SUFFIX); 151 return createRemoteFile(name, remoteFileClass); 152 } 153 catch (ClassNotFoundException | StorageException e) { 154 logger.log(Level.INFO, "Invalid filename for remote file " + name); 155 throw new StorageException("Invalid filename for remote file " + name); 156 } 157 } 158 159 @Override 160 public boolean equals(Object obj) { 161 if (this == obj) { 162 return true; 163 } 164 165 if (obj == null || !(obj instanceof RemoteFile)) { 166 return false; 167 } 168 169 RemoteFile other = (RemoteFile) obj; 170 171 if (name == null) { 172 if (other.name != null) { 173 return false; 174 } 175 } 176 else if (!name.equals(other.name)) { 177 return false; 178 } 179 180 return true; 181 } 182 183 @Override 184 public int hashCode() { 185 final int prime = 31; 186 int result = 1; 187 result = prime * result + ((name == null) ? 0 : name.hashCode()); 188 return result; 189 } 190 191 @Override 192 public String toString() { 193 return RemoteFile.class.getSimpleName() + "[name=" + name + "]"; 194 } 195}