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 org.opencastproject.util.data.Function;
27  
28  import java.io.Serializable;
29  import java.util.Objects;
30  
31  import javax.xml.bind.annotation.adapters.XmlAdapter;
32  import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
33  
34  /**
35   * ELement flavors describe {@link MediaPackageElement}s in a semantic way. They reveal or give at least a hint about
36   * the meaning of an element.
37   *
38   */
39  @XmlJavaTypeAdapter(MediaPackageElementFlavor.FlavorAdapter.class)
40  public class MediaPackageElementFlavor implements Cloneable, Comparable<MediaPackageElementFlavor>, Serializable {
41  
42    /**
43     * Wildcard character used in type and subtype
44     */
45    public static final String WILDCARD = "*";
46  
47    /**
48     * Serial version uid
49     */
50    private static final long serialVersionUID = 1L;
51  
52    /**
53     * Character that separates both parts of a flavor
54     */
55    public static final String SEPARATOR = "/";
56  
57    /**
58     * String representation of type
59     */
60    private String type = null;
61  
62    /**
63     * String representation of subtype
64     */
65    private String subtype = null;
66  
67  
68    private MediaPackageElementFlavor() {
69    }
70  
71    /**
72     * Creates a new flavor with the given type and subtype.
73     *
74     * @param type
75     *          the type of the flavor
76     * @param subtype
77     *          the subtype of the flavor
78     */
79    public MediaPackageElementFlavor(String type, String subtype) {
80      this.type = checkPartSyntax(type);
81      this.subtype = checkPartSyntax(subtype);
82    }
83  
84    /**
85     * Checks that any of the parts this flavor consists of abide to the syntax restrictions
86     *
87     * @param part
88     * @return
89     */
90    private String checkPartSyntax(String part) {
91      // Parts may not be null
92      if (part == null)
93        throw new IllegalArgumentException("Flavor parts may not be null!");
94  
95      // Parts may not contain the flavor separator character
96      if (part.contains(SEPARATOR))
97        throw new IllegalArgumentException(
98                format("Invalid flavor part \"%s\". Flavor parts may not contain '%s'!", part, SEPARATOR));
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     return adaptedPart;
109   }
110 
111   /** Constructor function for {@link #MediaPackageElementFlavor(String, String)}. */
112   public static MediaPackageElementFlavor flavor(String type, String subtype) {
113     return new MediaPackageElementFlavor(type, subtype);
114   }
115 
116   /**
117    * Returns the type of this flavor.
118    * The type is never <code>null</code>.
119    * <p>
120    * For example, if the type is a presentation movie which is represented as <code>presentation/source</code>,
121    * this method will return <code>presentation</code>.
122    *
123    * @return the type of this flavor
124    */
125   public String getType() {
126     return type;
127   }
128 
129   /**
130    * Returns the subtype of this flavor.
131    * The subtype is never <code>null</code>.
132    * <p>
133    * For example, if the subtype is a presentation movie which is represented as <code>presentation/source</code>,
134    * this method will return <code>source</code>.
135    *
136    * @return the subtype
137    */
138   public String getSubtype() {
139     return subtype;
140   }
141 
142   /**
143    * "Applies" this flavor to the given target flavor. E.g. applying '*\/preview' to 'presenter/source' yields
144    * 'presenter/preview', applying 'presenter/*' to 'foo/source' yields 'presenter/source', and applying 'foo/bar' to
145    * 'presenter/source' yields 'foo/bar'.
146    *
147    * @param target The target flavor to apply this flavor to.
148    *
149    * @return The resulting flavor.
150    */
151   public MediaPackageElementFlavor applyTo(MediaPackageElementFlavor target) {
152     String type = this.type;
153     String subtype = this.subtype;
154     if (WILDCARD.equals(this.type)) {
155       type = target.getType();
156     }
157     if (WILDCARD.equals(this.subtype)) {
158       subtype = target.getSubtype();
159     }
160     return new MediaPackageElementFlavor(type, subtype);
161   }
162 
163   /**
164    * @see java.lang.Object#clone()
165    */
166   @Override
167   public MediaPackageElementFlavor clone() throws CloneNotSupportedException {
168     MediaPackageElementFlavor m = (MediaPackageElementFlavor) super.clone();
169     m.type = this.type;
170     m.subtype = this.subtype;
171     return m;
172   }
173 
174   /**
175    * Defines equality between flavors and strings.
176    *
177    * @param flavor
178    *          string of the form "type/subtype"
179    */
180   public boolean eq(String flavor) {
181     return flavor != null && flavor.equals(toString());
182   }
183 
184   /**
185    * @see java.lang.String#compareTo(java.lang.Object)
186    */
187   @Override
188   public int compareTo(MediaPackageElementFlavor m) {
189     return toString().compareTo(m.toString());
190   }
191 
192   /**
193    * Returns the flavor as a string "type/subtype".
194    */
195   @Override
196   public String toString() {
197     return type + SEPARATOR + subtype;
198   }
199 
200   /**
201    * Creates a new media package element flavor.
202    *
203    * @param s
204    *          the media package flavor
205    * @return the media package element flavor object
206    * @throws IllegalArgumentException
207    *           if the string <code>s</code> does not contain a <i>dash</i> to divide the type from subtype.
208    */
209   public static MediaPackageElementFlavor parseFlavor(String s) throws IllegalArgumentException {
210     if (s == null)
211       throw new IllegalArgumentException("Unable to create element flavor from 'null'");
212     String[] parts = s.split(SEPARATOR);
213     if (parts.length != 2)
214       throw new IllegalArgumentException(format("Unable to create element flavor from \"%s\"", s));
215     return new MediaPackageElementFlavor(parts[0], parts[1]);
216   }
217 
218   public static final Function<String, MediaPackageElementFlavor> parseFlavor = new Function<String, MediaPackageElementFlavor>() {
219     @Override
220     public MediaPackageElementFlavor apply(String s) {
221       return parseFlavor(s);
222     }
223   };
224 
225   /**
226    * JAXB adapter implementation.
227    */
228   static class FlavorAdapter extends XmlAdapter<String, MediaPackageElementFlavor> {
229     @Override
230     public String marshal(MediaPackageElementFlavor flavor) throws Exception {
231       if (flavor == null) {
232         return null;
233       }
234       return flavor.toString();
235     }
236 
237     @Override
238     public MediaPackageElementFlavor unmarshal(String str) throws Exception {
239       return parseFlavor(str);
240     }
241   }
242 
243   /**
244    * Check if types match.
245    * Two types match if they are equal or if one of them is a {@link #WILDCARD wildcard}.
246    **/
247   private static boolean typeMatches(String a, String b) {
248     return a.equals(b) || WILDCARD.equals(a) || WILDCARD.equals(b);
249   }
250 
251   /**
252    * Check if two flavors match.
253    * Two flavors match if both their type and subtype matches.
254    *
255    * @param other
256    *          Flavor to compare against
257    * @return  If the flavors match
258    */
259   public boolean matches(MediaPackageElementFlavor other) {
260     return other != null
261       && typeMatches(type, other.type)
262       && typeMatches(subtype, other.subtype);
263   }
264 
265   @Override
266   public int hashCode() {
267     return Objects.hash(type, subtype);
268   }
269 
270   @Override
271   public boolean equals(Object other) {
272     return (other instanceof MediaPackageElementFlavor)
273       && type.equals(((MediaPackageElementFlavor) other).type)
274       && subtype.equals(((MediaPackageElementFlavor) other).subtype);
275   }
276 
277 }