View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.mediapackage;
23  
24  import static java.lang.String.format;
25  
26  import java.io.Serializable;
27  import java.util.Objects;
28  
29  import javax.xml.bind.annotation.adapters.XmlAdapter;
30  import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
31  
32  /**
33   * ELement flavors describe {@link MediaPackageElement}s in a semantic way. They reveal or give at least a hint about
34   * the meaning of an element.
35   *
36   */
37  @XmlJavaTypeAdapter(MediaPackageElementFlavor.FlavorAdapter.class)
38  public class MediaPackageElementFlavor implements Cloneable, Comparable<MediaPackageElementFlavor>, Serializable {
39  
40    /**
41     * Wildcard character used in type and subtype
42     */
43    public static final String WILDCARD = "*";
44  
45    /**
46     * Serial version uid
47     */
48    private static final long serialVersionUID = 1L;
49  
50    /**
51     * Character that separates both parts of a flavor
52     */
53    public static final String SEPARATOR = "/";
54  
55    /**
56     * String representation of type
57     */
58    private String type = null;
59  
60    /**
61     * String representation of subtype
62     */
63    private String subtype = null;
64  
65  
66    private MediaPackageElementFlavor() {
67    }
68  
69    /**
70     * Creates a new flavor with the given type and subtype.
71     *
72     * @param type
73     *          the type of the flavor
74     * @param subtype
75     *          the subtype of the flavor
76     */
77    public MediaPackageElementFlavor(String type, String subtype) {
78      this.type = checkPartSyntax(type);
79      this.subtype = checkPartSyntax(subtype);
80    }
81  
82    /**
83     * Checks that any of the parts this flavor consists of abide to the syntax restrictions
84     *
85     * @param part
86     * @return
87     */
88    private String checkPartSyntax(String part) {
89      // Parts may not be null
90      if (part == null) {
91        throw new IllegalArgumentException("Flavor parts may not be null!");
92      }
93  
94      // Parts may not contain the flavor separator character
95      if (part.contains(SEPARATOR)) {
96        throw new IllegalArgumentException(
97                format("Invalid flavor part \"%s\". Flavor parts may not contain '%s'!", part, SEPARATOR));
98      }
99  
100     // Parts may not contain leading and trailing blanks, and may only consist of lowercase letters
101     String adaptedPart = part.trim().toLowerCase();
102 
103     // Parts may not be empty
104     if (adaptedPart.isEmpty()) {
105       throw new IllegalArgumentException(
106           format("Invalid flavor part \"%s\". Flavor parts may not be blank or empty!", part));
107     }
108 
109     return adaptedPart;
110   }
111 
112   /** Constructor function for {@link #MediaPackageElementFlavor(String, String)}. */
113   public static MediaPackageElementFlavor flavor(String type, String subtype) {
114     return new MediaPackageElementFlavor(type, subtype);
115   }
116 
117   /**
118    * Returns the type of this flavor.
119    * The type is never <code>null</code>.
120    * <p>
121    * For example, if the type is a presentation movie which is represented as <code>presentation/source</code>,
122    * this method will return <code>presentation</code>.
123    *
124    * @return the type of this flavor
125    */
126   public String getType() {
127     return type;
128   }
129 
130   /**
131    * Returns the subtype of this flavor.
132    * The subtype is never <code>null</code>.
133    * <p>
134    * For example, if the subtype is a presentation movie which is represented as <code>presentation/source</code>,
135    * this method will return <code>source</code>.
136    *
137    * @return the subtype
138    */
139   public String getSubtype() {
140     return subtype;
141   }
142 
143   /**
144    * "Applies" this flavor to the given target flavor. E.g. applying '*\/preview' to 'presenter/source' yields
145    * 'presenter/preview', applying 'presenter/*' to 'foo/source' yields 'presenter/source', and applying 'foo/bar' to
146    * 'presenter/source' yields 'foo/bar'.
147    *
148    * @param target The target flavor to apply this flavor to.
149    *
150    * @return The resulting flavor.
151    */
152   public MediaPackageElementFlavor applyTo(MediaPackageElementFlavor target) {
153     String type = this.type;
154     String subtype = this.subtype;
155     if (WILDCARD.equals(this.type)) {
156       type = target.getType();
157     }
158     if (WILDCARD.equals(this.subtype)) {
159       subtype = target.getSubtype();
160     }
161     return new MediaPackageElementFlavor(type, subtype);
162   }
163 
164   /**
165    * @see java.lang.Object#clone()
166    */
167   @Override
168   public MediaPackageElementFlavor clone() throws CloneNotSupportedException {
169     MediaPackageElementFlavor m = (MediaPackageElementFlavor) super.clone();
170     m.type = this.type;
171     m.subtype = this.subtype;
172     return m;
173   }
174 
175   /**
176    * Defines equality between flavors and strings.
177    *
178    * @param flavor
179    *          string of the form "type/subtype"
180    */
181   public boolean eq(String flavor) {
182     return flavor != null && flavor.equals(toString());
183   }
184 
185   /**
186    * @see java.lang.String#compareTo(java.lang.Object)
187    */
188   @Override
189   public int compareTo(MediaPackageElementFlavor m) {
190     return toString().compareTo(m.toString());
191   }
192 
193   /**
194    * Returns the flavor as a string "type/subtype".
195    */
196   @Override
197   public String toString() {
198     return type + SEPARATOR + subtype;
199   }
200 
201   /**
202    * Creates a new media package element flavor.
203    *
204    * @param s
205    *          the media package flavor
206    * @return the media package element flavor object
207    * @throws IllegalArgumentException
208    *           if the string <code>s</code> does not contain a <i>dash</i> to divide the type from subtype.
209    */
210   public static MediaPackageElementFlavor parseFlavor(String s) throws IllegalArgumentException {
211     if (s == null) {
212       throw new IllegalArgumentException("Unable to create element flavor from 'null'");
213     }
214     String[] parts = s.split(SEPARATOR);
215     if (parts.length != 2) {
216       throw new IllegalArgumentException(format("Unable to create element flavor from \"%s\"", s));
217     }
218     return new MediaPackageElementFlavor(parts[0], parts[1]);
219   }
220 
221   /**
222    * JAXB adapter implementation.
223    */
224   static class FlavorAdapter extends XmlAdapter<String, MediaPackageElementFlavor> {
225     @Override
226     public String marshal(MediaPackageElementFlavor flavor) throws Exception {
227       if (flavor == null) {
228         return null;
229       }
230       return flavor.toString();
231     }
232 
233     @Override
234     public MediaPackageElementFlavor unmarshal(String str) throws Exception {
235       return parseFlavor(str);
236     }
237   }
238 
239   /**
240    * Check if types match.
241    * Two types match if they are equal or if one of them is a {@link #WILDCARD wildcard}.
242    **/
243   private static boolean typeMatches(String a, String b) {
244     return a.equals(b) || WILDCARD.equals(a) || WILDCARD.equals(b);
245   }
246 
247   /**
248    * Check if two flavors match.
249    * Two flavors match if both their type and subtype matches.
250    *
251    * @param other
252    *          Flavor to compare against
253    * @return  If the flavors match
254    */
255   public boolean matches(MediaPackageElementFlavor other) {
256     return other != null
257       && typeMatches(type, other.type)
258       && typeMatches(subtype, other.subtype);
259   }
260 
261   @Override
262   public int hashCode() {
263     return Objects.hash(type, subtype);
264   }
265 
266   @Override
267   public boolean equals(Object other) {
268     return (other instanceof MediaPackageElementFlavor)
269       && type.equals(((MediaPackageElementFlavor) other).type)
270       && subtype.equals(((MediaPackageElementFlavor) other).subtype);
271   }
272 
273 }