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