001/**
002 *
003 * Copyright 2020 Paul Schaub
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.file_metadata.element;
018
019import java.io.UnsupportedEncodingException;
020import java.net.URLEncoder;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027
028import org.jivesoftware.smack.packet.ExtensionElement;
029import org.jivesoftware.smack.packet.XmlEnvironment;
030import org.jivesoftware.smack.util.CollectionUtil;
031import org.jivesoftware.smack.util.EqualsUtil;
032import org.jivesoftware.smack.util.HashCode;
033import org.jivesoftware.smack.util.StringUtils;
034import org.jivesoftware.smack.util.XmlStringBuilder;
035import org.jivesoftware.smackx.hashes.HashManager;
036import org.jivesoftware.smackx.hashes.element.HashElement;
037import org.jivesoftware.smackx.thumbnails.element.ThumbnailElement;
038
039/**
040 * File metadata element as defined in XEP-0446: File Metadata Element.
041 * This element is used in a generic way to provide information about files, e.g. during file sharing.
042 */
043public final class FileMetadataElement implements ExtensionElement {
044
045    public static final String ELEMENT = "file";
046    public static final String NAMESPACE = "urn:xmpp:file:metadata:0";
047    public static final String ELEM_DATE = "date";
048    public static final String ELEM_HEIGHT = "height";
049    public static final String ELEM_WIDTH = "width";
050    public static final String ELEM_DESC = "desc";
051    public static final String ELEM_LENGTH = "length";
052    public static final String ELEM_MEDIA_TYPE = "media-type";
053    public static final String ELEM_NAME = "name";
054    public static final String ELEM_SIZE = "size";
055
056
057    private final Date date;
058    private final Integer height;
059    private final Integer width;
060    private final Map<String, String> descriptions;
061    private final Map<HashManager.ALGORITHM, HashElement> hashElements;
062    private final Long length;
063    private final String mediaType;
064    private final String name;
065    private final Long size;
066    private final List<ThumbnailElement> thumbnails;
067
068    private FileMetadataElement(Date date, Integer height, Integer width, Map<String, String> descriptions,
069                               Map<HashManager.ALGORITHM, HashElement> hashElements, Long length,
070                               String mediaType, String name, Long size,
071                               List<ThumbnailElement> thumbnails) {
072        this.date = date;
073        this.height = height;
074        this.width = width;
075        this.descriptions = CollectionUtil.cloneAndSeal(descriptions);
076        this.hashElements = CollectionUtil.cloneAndSeal(hashElements);
077        this.length = length;
078        this.mediaType = mediaType;
079        this.name = name;
080        this.size = size;
081        this.thumbnails = CollectionUtil.cloneAndSeal(thumbnails);
082    }
083
084    @Override
085    public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
086        XmlStringBuilder sb = new XmlStringBuilder(this)
087                .rightAngleBracket()
088                .optElement(ELEM_DATE, date)
089                .optElement(ELEM_HEIGHT, height)
090                .optElement(ELEM_WIDTH, width);
091        for (String key : descriptions.keySet()) {
092            sb.halfOpenElement(ELEM_DESC)
093                    .optXmlLangAttribute(key)
094                    .rightAngleBracket()
095                    .append(descriptions.get(key))
096                    .closeElement(ELEM_DESC);
097        }
098        sb.append(hashElements.values())
099                .optElement(ELEM_LENGTH, length != null ? Long.toString(length) : null)
100                .optElement(ELEM_MEDIA_TYPE, mediaType)
101                .optElement(ELEM_NAME, name)
102                .optElement(ELEM_SIZE, size != null ? Long.toString(size) : null)
103                .append(thumbnails);
104        return sb.closeElement(this);
105    }
106
107    @Override
108    public String getNamespace() {
109        return NAMESPACE;
110    }
111
112    @Override
113    public String getElementName() {
114        return ELEMENT;
115    }
116
117    public Date getDate() {
118        return date;
119    }
120
121    public Integer getHeight() {
122        return height;
123    }
124
125    public Integer getWidth() {
126        return width;
127    }
128
129    public Map<String, String> getDescriptions() {
130        return Collections.unmodifiableMap(descriptions);
131    }
132
133    public String getDescription() {
134        return getDescription(getLanguage());
135    }
136
137    public String getDescription(String lang) {
138        return descriptions.get(lang != null ? lang : "");
139    }
140
141    public Map<HashManager.ALGORITHM, HashElement> getHashElements() {
142        return Collections.unmodifiableMap(hashElements);
143    }
144
145    public HashElement getHashElement(HashManager.ALGORITHM algorithm) {
146        return hashElements.get(algorithm);
147    }
148
149    public Long getLength() {
150        return length;
151    }
152
153    public String getMediaType() {
154        return mediaType;
155    }
156
157    /**
158     * Return the name of the file.
159     *
160     * @return escaped name
161     */
162    public String getName() {
163        if (name == null) {
164            return null;
165        }
166        try {
167            return URLEncoder.encode(name, "UTF-8");
168        } catch (UnsupportedEncodingException e) {
169            throw new AssertionError(e); // UTF-8 MUST be supported
170        }
171    }
172
173    public String getRawName() {
174        return name;
175    }
176
177    public Long getSize() {
178        return size;
179    }
180
181    public List<ThumbnailElement> getThumbnails() {
182        return Collections.unmodifiableList(thumbnails);
183    }
184
185    @Override
186    public int hashCode() {
187        return HashCode.builder()
188                .append(getElementName())
189                .append(getNamespace())
190                .append(getDate())
191                .append(getDescriptions())
192                .append(getHeight())
193                .append(getWidth())
194                .append(getHashElements())
195                .append(getLength())
196                .append(getMediaType())
197                .append(getRawName())
198                .append(getSize())
199                .append(getThumbnails())
200                .build();
201    }
202
203    @Override
204    public boolean equals(Object other) {
205        return EqualsUtil.equals(this, other, (equalsBuilder, o) -> equalsBuilder
206                .append(getElementName(), o.getElementName())
207                .append(getNamespace(), o.getNamespace())
208                .append(getDate(), o.getDate())
209                .append(getDescriptions(), o.getDescriptions())
210                .append(getHeight(), o.getHeight())
211                .append(getWidth(), o.getWidth())
212                .append(getHashElements(), o.getHashElements())
213                .append(getLength(), o.getLength())
214                .append(getMediaType(), o.getMediaType())
215                .append(getRawName(), o.getRawName())
216                .append(getSize(), o.getSize())
217                .append(getThumbnails(), o.getThumbnails()));
218    }
219
220    public static Builder builder() {
221        return new Builder();
222    }
223
224    public static class Builder {
225
226        private Date date;
227        private Integer height;
228        private Integer width;
229        private Map<String, String> descriptions = new HashMap<>();
230        private Map<HashManager.ALGORITHM, HashElement> hashElements = new HashMap<>();
231        private Long length;
232        private String mediaType;
233        private String name;
234        private Long size;
235        private List<ThumbnailElement> thumbnails = new ArrayList<>();
236
237        public Builder setModificationDate(Date date) {
238            this.date = date;
239            return this;
240        }
241
242        public Builder setDimensions(int width, int height) {
243            return setHeight(height).setWidth(width);
244        }
245
246        public Builder setHeight(int height) {
247            if (height <= 0) {
248                throw new IllegalArgumentException("Height must be a positive number");
249            }
250            this.height = height;
251            return this;
252        }
253
254        public Builder setWidth(int width) {
255            if (width <= 0) {
256                throw new IllegalArgumentException("Width must be a positive number");
257            }
258            this.width = width;
259            return this;
260        }
261
262        public Builder addDescription(String description) {
263            return addDescription(description, null);
264        }
265
266        public Builder addDescription(String description, String language) {
267            this.descriptions.put(language != null ? language : "", StringUtils.requireNotNullNorEmpty(description, "Description MUST NOT be null nor empty"));
268            return this;
269        }
270
271        public Builder addHash(HashElement hashElement) {
272            hashElements.put(hashElement.getAlgorithm(), hashElement);
273            return this;
274        }
275
276        public Builder setLength(long length) {
277            if (length < 0) {
278                throw new IllegalArgumentException("Length cannot be negative.");
279            }
280            this.length = length;
281            return this;
282        }
283
284        public Builder setMediaType(String mediaType) {
285            this.mediaType = StringUtils.requireNotNullNorEmpty(mediaType, "Media-Type MUST NOT be null nor empty");
286            return this;
287        }
288
289        public Builder setName(String name) {
290            this.name = StringUtils.requireNotNullNorEmpty(name, "Name MUST NOT be null nor empty");
291            return this;
292        }
293
294        public Builder setSize(long size) {
295            if (size < 0) {
296                throw new IllegalArgumentException("Size MUST NOT be negative.");
297            }
298            this.size = size;
299            return this;
300        }
301
302        public Builder addThumbnail(ThumbnailElement thumbnail) {
303            thumbnails.add(thumbnail);
304            return this;
305        }
306
307        public FileMetadataElement build() {
308            return new FileMetadataElement(date, height, width, descriptions, hashElements, length,
309                    mediaType, name, size, thumbnails);
310        }
311    }
312}