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.database;
019
020import java.util.Collections;
021import java.util.Map;
022import java.util.TreeMap;
023
024/**
025 * A <code>PartialFileHistory</code> represents a single file in a repository over a
026 * certain period of time/versions. Whenever a file is updated or deleted, a new
027 * {@link FileVersion} is added to the file history.
028 *
029 * <p>A file history is identified by a unique random identifier and holds a sorted
030 * list of file versions.
031 *
032 * <p>Due to cleanup mechanisms and the delta database concept, the list of file
033 * versions is not always complete. The class hence represents a part of the file
034 * history.
035 *
036 * @see FileVersion
037 * @author Philipp C. Heckel (philipp.heckel@gmail.com)
038 * @author Fabrice Rossi
039 */
040public class PartialFileHistory {
041        private static final byte FILE_HISTORY_ID_LENGTH = 20;
042
043        private FileHistoryId fileHistoryId;
044        private TreeMap<Long, FileVersion> versions;
045
046        public PartialFileHistory() {
047                // Required for SimpleXML.
048        }
049
050        /**
051         * Creates a new file history instance, given a {@link FileHistoryId} as identifier
052         * of the file over time. After creation, the file history's versions map is empty.
053         *
054         * @param fileHistoryId Random or non-random file history identifier
055         * @throws IllegalArgumentException If fileHistoryId is null
056         */
057        public PartialFileHistory(FileHistoryId fileHistoryId) {
058                if (fileHistoryId == null) {
059                        throw new IllegalArgumentException("Argument fileHistoryId cannot be null.");
060                }
061
062                this.fileHistoryId = fileHistoryId;
063                this.versions = new TreeMap<Long, FileVersion>();
064        }
065
066        /**
067         * Returns the file history identifier for this file history. Note that
068         * this value cannot be null.
069         */
070        public FileHistoryId getFileHistoryId() {
071                return fileHistoryId;
072        }
073
074        /**
075         * Returns an unmodifiable map of the {@link FileVersion}s, keyed by the
076         * version number of the corresponding file version.
077         */
078        public Map<Long, FileVersion> getFileVersions() {
079                return Collections.unmodifiableMap(versions);
080        }
081
082        /**
083         * Returns the file version with the given file version number, or null if
084         * a version with this number does not exist in this file history.
085         */
086        public FileVersion getFileVersion(long version) {
087                return versions.get(version);
088        }
089
090        /**
091         * Returns the last file version in this instance of the partial file history,
092         * or <code>null</code> if there are no file versions.
093         *
094         * <p>Note that this method does not necessarily return the actual overall
095         * last file version, only the last of this object instance.
096         *
097         * @return Returns the last file version, or <code>null</code>
098         */
099        public FileVersion getLastVersion() {
100                if (versions.isEmpty()) {
101                        return null;
102                }
103
104                return versions.lastEntry().getValue();
105        }
106
107        /**
108         * Adds a new file version of the file history. The given file version is added
109         * to an internal tree map, sorted by the attribute {@link FileVersion#getVersion()}.
110         *
111         * If a file version version with the same version already exists, it is replaced by
112         * the given file version.
113         *
114         * @param fileVersion File version to be added to the file history
115         * @throws IllegalArgumentException If fileVersion or its version number is <code>null</code>
116         */
117        public void addFileVersion(FileVersion fileVersion) {
118                if (fileVersion == null || fileVersion.getVersion() == null) {
119                        throw new IllegalArgumentException("Argument fileVersion or fileVersion.getVersion() cannot be null.");
120                }
121
122                versions.put(fileVersion.getVersion(), fileVersion);
123        }
124
125        /**
126         * Clones the file history, including its file versions. Note that file versions
127         * are not cloned, but copied by reference.
128         *
129         * @return Returns cloned file history
130         */
131        @Override
132        public PartialFileHistory clone() {
133                PartialFileHistory clone = new PartialFileHistory(fileHistoryId);
134                clone.versions.putAll(versions);
135
136                return clone;
137        }
138
139        @Override
140        public String toString() {
141                return PartialFileHistory.class.getSimpleName() + "(fileId=" + fileHistoryId + ", versions=" + versions + ")";
142        }
143
144        @Override
145        public int hashCode() {
146                final int prime = 31;
147                int result = 1;
148                result = prime * result + ((fileHistoryId == null) ? 0 : fileHistoryId.hashCode());
149                result = prime * result + ((versions == null) ? 0 : versions.hashCode());
150                return result;
151        }
152
153        @Override
154        public boolean equals(Object obj) {
155                if (this == obj) {
156                        return true;
157                }
158                if (obj == null) {
159                        return false;
160                }
161                if (!(obj instanceof PartialFileHistory)) {
162                        return false;
163                }
164                PartialFileHistory other = (PartialFileHistory) obj;
165                if (fileHistoryId == null) {
166                        if (other.fileHistoryId != null) {
167                                return false;
168                        }
169                }
170                else if (!fileHistoryId.equals(other.fileHistoryId)) {
171                        return false;
172                }
173                if (versions == null) {
174                        if (other.versions != null) {
175                                return false;
176                        }
177                }
178                else if (!versions.equals(other.versions)) {
179                        return false;
180                }
181                return true;
182        }
183
184        /**
185         * The file history identifier (also: file identifier) is a key to identify a single file
186         * throughout its lifetime. In particular, it does not only identify
187         *
188         */
189        public static class FileHistoryId extends ObjectId {
190                private FileHistoryId(byte[] array) {
191                        super(array);
192                }
193
194                public static FileHistoryId secureRandomFileId() {
195                        return new FileHistoryId(ObjectId.secureRandomBytes(FILE_HISTORY_ID_LENGTH));
196                }
197
198                public static FileHistoryId parseFileId(String s) {
199                        return new FileHistoryId(ObjectId.parseObjectId(s));
200                }
201        }
202}