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}