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}