/* Copyright (c) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gdata.client.youtube;
import com.google.gdata.client.Query;
import com.google.gdata.data.geo.impl.GeoRssWhere;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A helper class that helps building queries for the
* YouTube feeds.
*
* Not all feeds implement all parameters defined on
* this class. See the documentation to get the list
* of parameters each feed supports.
*
*
*/
public class YouTubeQuery extends Query {
private static final String VQ = "vq";
private static final String TIME = "time";
private static final String FORMAT = "format";
private static final String ORDERBY = "orderby";
private static final String RACY = "racy";
private static final String RACY_INCLUDE = "include";
private static final String RACY_EXCLUDE = "exclude";
private static final String LANGUAGE_RESTRICT = "lr";
private static final String RESTRICTION = "restriction";
private static final String LOCATION = "location";
private static final String LOCATION_RADIUS = "location-radius";
private static final String SAFE_SEARCH = "safeSearch";
private static final String UPLOADER = "uploader";
private static final Pattern COUNTRY_CODE_PATTERN = Pattern.compile("[a-zA-Z]{2}");
private static final Pattern IP_V4_PATTERN
= Pattern.compile("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
private static final Pattern LOCATION_RADIUS_PATTERN
= Pattern.compile("\\d+(ft|mi|m|km)");
/**
* Standard values for the {@code time} parameter.
*/
public static enum Time {
TODAY("today"),
THIS_WEEK("this_week"),
THIS_MONTH("this_month"),
ALL_TIME("all_time");
private final String value;
private Time(String value) {
this.value = value;
}
/** Returns the corresponding parameter value. */
public String toParameterValue() {
return value;
}
public static Time fromParameterValue(String value) {
if (value == null) {
return null;
}
Time time = PARAMETER_TO_TIME.get(value);
if (time == null) {
throw new IllegalStateException("Cannot convert time value: " + value);
}
return time;
}
private static Map<String, Time> PARAMETER_TO_TIME;
static {
Map<String, Time> map = new HashMap<String, Time>();
for (Time time : Time.values()) {
map.put(time.toParameterValue(), time);
}
PARAMETER_TO_TIME = Collections.unmodifiableMap(map);
}
}
/**
* Standard values for the {@code orderby} parameter.
*/
public static enum OrderBy {
RELEVANCE("relevance"),
/**
* @deprecated use {@link #PUBLISHED} instead.
*/
@Deprecated
UPDATED("updated"),
VIEW_COUNT("viewCount"),
RATING("rating"),
PUBLISHED("published");
private final String value;
private OrderBy(String value) {
this.value = value;
}
/** Returns the corresponding parameter value. */
public String toParameterValue() {
return value;
}
public static OrderBy fromParameterValue(String value) {
if (value == null) {
return null;
}
OrderBy orderBy = PARAMETER_TO_ORDERBY.get(value);
if (orderBy == null) {
throw new IllegalStateException("Cannot convert orderBy value: "
+ value);
}
return orderBy;
}
private static Map<String, OrderBy> PARAMETER_TO_ORDERBY;
static {
Map<String, OrderBy> map = new HashMap<String, OrderBy>();
for (OrderBy orderBy : OrderBy.values()) {
map.put(orderBy.toParameterValue(), orderBy);
}
PARAMETER_TO_ORDERBY = Collections.unmodifiableMap(map);
}
}
/**
* Standard values for the {@code safeSearch} parameter.
*/
public static enum SafeSearch {
NONE("none"),
MODERATE("moderate"),
STRICT("strict");
private final String value;
private SafeSearch(String value) {
this.value = value;
}
/** Returns the corresponding parameter value. */
public String toParameterValue() {
return value;
}
public static SafeSearch fromParameterValue(String value) {
if (value == null) {
return null;
}
SafeSearch safeSearch = PARAMETER_TO_SAFESEARCH.get(value);
if (safeSearch == null) {
throw new IllegalStateException("Cannot convert safeSearch value: "
+ value);
}
return safeSearch;
}
private static Map<String, SafeSearch> PARAMETER_TO_SAFESEARCH;
static {
Map<String, SafeSearch> map = new HashMap<String, SafeSearch>();
for (SafeSearch safeSearch : SafeSearch.values()) {
map.put(safeSearch.toParameterValue(), safeSearch);
}
PARAMETER_TO_SAFESEARCH = Collections.unmodifiableMap(map);
}
}
/**
* Standard values for the {@code uploader} parameter.
*/
public static enum Uploader {
PARTNER("partner");
private final String value;
private Uploader(String value) {
this.value = value;
}
/** Returns the corresponding parameter value. */
public String toParameterValue() {
return value;
}
public static Uploader fromParameterValue(String value) {
if (value == null) {
return null;
}
Uploader uploader = PARAMETER_TO_UPLOADER.get(value);
if (uploader == null) {
throw new IllegalStateException("Cannot convert uploader value: "
+ value);
}
return uploader;
}
private static Map<String, Uploader> PARAMETER_TO_UPLOADER;
static {
Map<String, Uploader> map = new HashMap<String, Uploader>();
for (Uploader uploader : Uploader.values()) {
map.put(uploader.toParameterValue(), uploader);
}
PARAMETER_TO_UPLOADER = Collections.unmodifiableMap(map);
}
}
/**
* Prefix for specifying relevance by language.
*/
private static final Pattern RELEVANCE_LANGUAGE_PATTERN = Pattern.compile("_lang_([^_]+)");
/**
* Constructs a new YouTubeQuery object that targets a feed. The initial
* state of the query contains no parameters, meaning all entries
* in the feed would be returned if the query was executed immediately
* after construction.
*
* @param feedUrl the URL of the feed against which queries will be
* executed.
*/
public YouTubeQuery(URL feedUrl) {
super(feedUrl);
}
/**
* Gets the value of the {@code vq} parameter.
*
* @return current query string
* @deprecated Please use {@link Query#getFullTextQuery()} instead.
*/
@Deprecated
public String getVideoQuery() {
return getCustomParameterValue(VQ);
}
/**
* Sets the value of the {@code vq} parameter.
*
* The {@code vq} parameter is exactly equivalent to the
* {@code q} parameter.
*
* @param query query string, {@code null} to remove the parameter
* @deprecated Please use {@link Query#setFullTextQuery()} instead.
*/
@Deprecated
public void setVideoQuery(String query) {
overwriteCustomParameter(VQ, query);
}
/**
* Gets the value of the {@code time} parameter.
*
* @return value of the {@code time} parameter
* @throws IllegalStateException if a time value was found in the
* query that cannot be transformed into {@link YouTubeQuery.Time}
*/
public Time getTime() {
return Time.fromParameterValue(getCustomParameterValue(TIME));
}
/**
* Sets the value of the {@code time} parameter.
*
* @param time time value, {@code null} to remove the parameter
*/
public void setTime(Time time) {
overwriteCustomParameter(TIME,
time == null ? null : time.toParameterValue());
}
/**
* Gets the value of the {@code format} parameter.
*
* @return all defined formats, might be empty but not null
* @throws NumberFormatException if the current value is
* invalid.
*/
public Set<Integer> getFormats() {
String value = getCustomParameterValue(FORMAT);
if (value == null) {
return Collections.emptySet();
}
Set<Integer> retval = new LinkedHashSet<Integer>();
String[] formats = value.trim().split(" *, *");
for (String format : formats) {
retval.add(new Integer(format));
}
return retval;
}
/**
* Sets the value of the {@code format} parameter.
*
* See the documentation for a description of the
* different formats that are be available.
*
* @param formats integer id of all the formats you are
* interested in. Videos will be returned if and only
* if they have downloadable content for at least one
* of these formats. No formats removes the parameter.
*/
public void setFormats(int... formats) {
Set<Integer> formatSet = new LinkedHashSet<Integer>();
for (int format : formats) {
formatSet.add(format);
}
setFormats(formatSet);
}
/**
* Sets the value of the {@code format} parameter.
*
* See the documentation for a description of the
* different formats that are be available.
*
* @param formats integer id of all the formats you are interested
* in. Videos will be returned if and only if they have
* downloadable content for at least one of these formats. {@code
* null} or an empty set removes the parameter
*/
public void setFormats(Set<Integer> formats) {
if (formats == null || formats.isEmpty()) {
overwriteCustomParameter(FORMAT, null);
return;
}
StringBuilder stringValue = new StringBuilder();
boolean isFirst = true;
for (int format : formats) {
if (isFirst) {
isFirst = false;
} else {
stringValue.append(',');
}
stringValue.append(format);
}
overwriteCustomParameter(FORMAT, stringValue.toString());
}
/**
* Sets the value of the {@code lr} parameter.
*
* This parameters restricts the videos that are returned
* to videos with its title, description and tags mostly
* in the specified language.
* It might be different from the language of the video itself.
*
* @param languageCode <a
* href="http://www.loc.gov/standards/iso639-2/php/code_list.php">
* ISO 639-1 2-letter language code</a>. {@code zh-Hans} for simplified
* chinese, {@code zh-Hant} for traditional chinese.
*/
public void setLanguageRestrict(String languageCode) {
overwriteCustomParameter(LANGUAGE_RESTRICT, languageCode);
}
/**
* Gets the value of the {@code lr} parameter.
*
* @return value of the {@code lr} parameter; a language code
*/
public String getLanguageRestrict() {
return getCustomParameterValue(LANGUAGE_RESTRICT);
}
/**
* Gets the value of the {@code orderby} parameter.
*
* @return value of the {@code orderby} parameter.
* @throws IllegalStateException if a time value was found in the
* query that cannot be transformed into {@link YouTubeQuery.OrderBy}
*/
public OrderBy getOrderby() {
String stringValue = getCustomParameterValue(ORDERBY);
if (stringValue != null && stringValue.startsWith("relevance_")) {
return OrderBy.RELEVANCE;
}
return OrderBy.fromParameterValue(stringValue);
}
/**
* Sets the value of the {@code orderby} parameter.
*
* @param orderBy value of the {@code orderby} parameter,
* {@code null} to remove the parameter
*/
public void setOrderBy(OrderBy orderBy) {
overwriteCustomParameter(ORDERBY,
orderBy == null ? null : orderBy.toParameterValue());
}
/**
* Sets order by relevance with results optimized for a specific
* language.
*
* @param languageCode {@code null} or <a
* href="http://www.loc.gov/standards/iso639-2/php/code_list.php">
* ISO 639-1 2-letter language code</a>. {@code zh-Hans} for simplified
* chinese, {@code zh-Hant} for traditional chinese.
*/
public void setOrderByRelevanceForLanguage(String languageCode) {
overwriteCustomParameter(ORDERBY,
languageCode == null
? OrderBy.RELEVANCE.toParameterValue() : "relevance_lang_" + languageCode);
}
/**
* Gets the language for which relevance ordering is optimized.
*
* @return a language code as specified to
* {@link #setOrderByRelevanceForLanguage} or {@code null}
* @throws IllegalStateException if ordering is not set to relevance
*/
public String getOrderByRelevanceForLanguage() {
String stringValue = getCustomParameterValue(ORDERBY);
if (stringValue == null) {
// Default: order by relevance, no specific language
return null;
}
if (getOrderby() != OrderBy.RELEVANCE) {
throw new IllegalStateException("Not ordering by relevance. Please"
+ " check with getOrderBy() first");
}
if (stringValue == null) {
return null;
}
Matcher matcher = RELEVANCE_LANGUAGE_PATTERN.matcher(stringValue);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
/**
* Gets the value of the {@code safeSearch} parameter.
*
* @return value of the {@code safeSearch} parameter.
* @throws IllegalStateException if a value was found in the
* query that cannot be transformed into {@link YouTubeQuery.SafeSearch}
*/
public SafeSearch getSafeSearch() {
String stringValue = getCustomParameterValue(SAFE_SEARCH);
return SafeSearch.fromParameterValue(stringValue);
}
/**
* Sets the value of the {@code safeSearch} parameter.
*
* @param safeSearch value of {@code safeSearch} parameter,
* {@code null} to remove the parameter
*/
public void setSafeSearch(SafeSearch safeSearch) {
overwriteCustomParameter(SAFE_SEARCH,
safeSearch == null ? null : safeSearch.toParameterValue());
}
/**
* Gets the value of the {@code racy} parameter.
*
* @return true if the {@code racy=include} parameter is present
* @deprecated Please use {@link #getSafeSearch()} instead.
*/
@Deprecated
public boolean getIncludeRacy() {
return RACY_INCLUDE.equals(getCustomParameterValue(RACY));
}
/**
* Sets the value of the {@code racy} parameter.
*
* @param includeRacy {@code true} to include racy content, false
* to exclude it, {@code null} to remove the parameter
* @deprecated Please use {@link #setSafeSearch(String)} instead.
*/
@Deprecated
public void setIncludeRacy(Boolean includeRacy) {
String stringValue;
if (includeRacy == null) {
stringValue = null;
} else {
stringValue = includeRacy ? RACY_INCLUDE : RACY_EXCLUDE;
}
overwriteCustomParameter(RACY, stringValue);
}
/**
* Sets the value of the {@code location} parameter.
* @param where A {@link com.google.gdata.data.geo.impl.GeoRssWhere}
* element describing the center of where to search.
*/
public void setLocation(GeoRssWhere where) {
StringBuilder location = new StringBuilder();
if (where != null) {
location.append(
where.getLatitude()).append(",").append(where.getLongitude());
}
if (hasRestrictLocation()) {
location.append("!");
}
overwriteCustomParameter(LOCATION,
location.toString().equals("") ? null : location.toString());
}
/**
* Returns the value of the {@code location} parameter.
* @return A {@link com.google.gdata.data.geo.impl.GeoRssWhere} element
* describing the center of where to search.
*/
public GeoRssWhere getLocation() {
String location = getCustomParameterValue(LOCATION);
location = location.replaceAll("!", "");
String[] parts = location.split(",");
return new GeoRssWhere(Double.parseDouble(parts[0]), Double.parseDouble(parts[1]));
}
/**
* Sets the value of the {@code location-radius} parameter. Format is
* "100km". Valid units of measurement are "ft", "mi", "m", and "km".
* @param locationRadius The requested search radius.
* @throws InvalidArgumentException if the given string is not a properly
* formatted location radius.
*/
public void setLocationRadius(String locationRadius) {
if (locationRadius != null && !LOCATION_RADIUS_PATTERN.matcher(locationRadius).matches()) {
throw new IllegalArgumentException("Invalid location radius: " + locationRadius);
}
overwriteCustomParameter(LOCATION_RADIUS, locationRadius);
}
/**
* Sets the value of the {@code location-radius} parameter. Format is
* "100km". Valid units of measurement are "ft", "mi", "m", and "km".
* @return The current search radius.
*/
public String getLocationRadius() {
return getCustomParameterValue(LOCATION_RADIUS);
}
/**
* Set/unset the location restrict.
* @param isRestrictLocation {@code true} if only videos that have
* latitude and longitude information are to be returned,
* {@code false} otherwise.
*/
public void setRestrictLocation(boolean isRestrictLocation) {
String location = getCustomParameterValue(LOCATION);
if (location == null) {
location = "";
}
if (isRestrictLocation) {
if (!location.endsWith("!")) {
overwriteCustomParameter(LOCATION, location + "!");
}
} else {
location = location.replaceAll("!", "");
//if we have no lat/long then remove the query parameter.
if (location.length() == 0) {
location = null;
}
overwriteCustomParameter(LOCATION, location);
}
}
/**
* Returns {@code true} if the query only wants results that have latitude
* and longitude information.
* @return {@code true} if the query is restricted by location, {@code false} otherwise.
*/
public boolean hasRestrictLocation() {
String location = getCustomParameterValue(LOCATION);
return location != null && location.endsWith("!");
}
/**
* Retrieves the country restriction set on the current query, if any.
*
* @return the current country restriction as a two letter ISO 3166 country
* code, or {@code null} if no country restriction is set.
*
* @see #getIpRestriction()
*/
public String getCountryRestriction() {
String restriction = getCustomParameterValue(RESTRICTION);
if (restriction == null) {
return null;
}
return COUNTRY_CODE_PATTERN.matcher(restriction).matches() ? restriction : null;
}
/**
* Sets the {@code restriction} parameter to a country code.
* <p>
* This parameter restricts the returned results to content available for
* clients in the specified country.
* <p>
* Only one of the {@link #setCountryRestriction(String)} or
* {@link #setIpRestriction(String)} should be used, using both will only take
* into consideration the last used.
*
* @param countryCode a two letter ISO-3166 country code, may be {@code null}
* to mean no restriction at all.
* @throws IllegalArgumentException if the given country code is not a well
* formated two letter country code.
*/
public void setCountryRestriction(String countryCode) {
if (countryCode != null && !COUNTRY_CODE_PATTERN.matcher(countryCode).matches()) {
throw new IllegalArgumentException("Invalid country code: " + countryCode);
}
overwriteCustomParameter(RESTRICTION, countryCode);
}
/**
* Retrieves the IP restriction set on the current query, if any.
*
* @return the current IP v4 restriction or {@code null} if no IP restriction
* is set.
*
* @see #getCountryRestriction()
*/
public String getIpRestriction() {
String restriction = getCustomParameterValue(RESTRICTION);
if (restriction == null) {
return null;
}
return IP_V4_PATTERN.matcher(restriction).matches() ? restriction : null;
}
/**
* Sets the {@code restriction} parameter to an IP v4 address.
* <p>
* This parameter restricts the returned results to content available for
* clients in the country that the provided IP address belongs to.
* <p>
* Only one of the {@link #setCountryRestriction(String)} or
* {@link #setIpRestriction(String)} should be used, using both will only take
* into consideration the last used.
*
* @param ip a v4 IP address and may be {@code null} to mean no restriction at
* all.
* @throws IllegalArgumentException if the given address is not a well
* formated IP v4 address.
*/
public void setIpRestriction(String ip) {
if (ip != null && !IP_V4_PATTERN.matcher(ip).matches()) {
throw new IllegalArgumentException("Invalid IP v4 address: " + ip);
}
overwriteCustomParameter(RESTRICTION, ip);
}
void overwriteCustomParameter(String name, String value) {
List<CustomParameter> customParams = getCustomParameters();
// Remove any existing value.
for (CustomParameter existingValue : getCustomParameters(name)) {
customParams.remove(existingValue);
}
// Add the specified value.
if (value != null) {
customParams.add(new CustomParameter(name, value));
}
}
String getCustomParameterValue(String parameterName) {
List<CustomParameter> customParams = getCustomParameters(parameterName);
if (customParams.isEmpty()) {
return null;
}
return customParams.get(0).getValue();
}
/**
* Gets the value of the {@code uploader} parameter.
*
* @return value of the {@code uploader} parameter.
* @throws IllegalStateException if a value was found in the
* query that cannot be transformed into {@link YouTubeQuery.Uploader}
*/
public Uploader getUploader() {
String stringValue = getCustomParameterValue(UPLOADER);
return Uploader.fromParameterValue(stringValue);
}
/**
* Sets the value of the {@code uploader} parameter.
*
* @param uploader value of the {@code uploader} parameter,
* {@code null} to remove the parameter
*/
public void setUploader(Uploader uploader) {
overwriteCustomParameter(UPLOADER,
uploader == null ? null : uploader.toParameterValue());
}
}