/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.wicket.core.request.mapper;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import org.apache.wicket.IRequestListener;
import org.apache.wicket.Session;
import org.apache.wicket.core.request.handler.BookmarkableListenerRequestHandler;
import org.apache.wicket.core.request.handler.BookmarkablePageRequestHandler;
import org.apache.wicket.core.request.handler.IPageRequestHandler;
import org.apache.wicket.core.request.handler.ListenerRequestHandler;
import org.apache.wicket.core.request.handler.PageAndComponentProvider;
import org.apache.wicket.core.request.handler.PageProvider;
import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
import org.apache.wicket.protocol.http.PageExpiredException;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.IRequestHandlerDelegate;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.component.IRequestablePage;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.http.WebRequest;
import org.apache.wicket.request.mapper.info.ComponentInfo;
import org.apache.wicket.request.mapper.info.PageComponentInfo;
import org.apache.wicket.request.mapper.info.PageInfo;
import org.apache.wicket.request.mapper.parameter.INamedParameters;
import org.apache.wicket.request.mapper.parameter.IPageParametersEncoder;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.request.mapper.parameter.PageParametersEncoder;
import org.apache.wicket.util.lang.Args;

/**
 * Abstract encoder for Bookmarkable, Hybrid and BookmarkableListener URLs.
 * 
 * @author Matej Knopp
 */
public abstract class AbstractBookmarkableMapper extends AbstractComponentMapper
{

	/**
	 * A flag that is used when comparing the mounted paths' segments against
	 * the request's url ones.
	 *
	 * @see #setCaseSensitiveMatch(boolean)
	 */
	private boolean isCaseSensitive = true;

	/**
	 * Represents information stored in URL.
	 * 
	 * @author Matej Knopp
	 */
	protected static final class UrlInfo
	{
		private final PageComponentInfo pageComponentInfo;
		private final PageParameters pageParameters;
		private final Class<? extends IRequestablePage> pageClass;

		/**
		 * Construct.
		 * 
		 * @param pageComponentInfo
		 *            optional parameter providing the page instance and component information
		 * @param pageClass
		 *            mandatory parameter
		 * @param pageParameters
		 *            optional parameter providing pageParameters
		 */
		public UrlInfo(PageComponentInfo pageComponentInfo,
			Class<? extends IRequestablePage> pageClass, PageParameters pageParameters)
		{
			Args.notNull(pageClass, "pageClass");

			this.pageComponentInfo = pageComponentInfo;
			this.pageParameters = cleanPageParameters(pageParameters);

			this.pageClass = pageClass;
		}

		/**
		 * Cleans the original parameters from entries used by Wicket internals.
		 * 
		 * @param originalParameters
		 *            the current request's non-modified parameters
		 * @return all parameters but Wicket internal ones
		 */
		private PageParameters cleanPageParameters(final PageParameters originalParameters)
		{
			PageParameters cleanParameters = null;
			if (originalParameters != null)
			{
				cleanParameters = new PageParameters(originalParameters);

				// WICKET-4038: Ajax related parameters are set by wicket-ajax.js when needed.
				// They shouldn't be propagated to the next requests
				cleanParameters.remove(WebRequest.PARAM_AJAX);
				cleanParameters.remove(WebRequest.PARAM_AJAX_BASE_URL);
				cleanParameters.remove(WebRequest.PARAM_AJAX_REQUEST_ANTI_CACHE);

				if (cleanParameters.isEmpty())
				{
					cleanParameters = null;
				}
			}
			return cleanParameters;
		}

		/**
		 * @return PageComponentInfo instance or <code>null</code>
		 */
		public PageComponentInfo getPageComponentInfo()
		{
			return pageComponentInfo;
		}

		/**
		 * @return page class
		 */
		public Class<? extends IRequestablePage> getPageClass()
		{
			return pageClass;
		}

		/**
		 * @return PageParameters instance (never <code>null</code>)
		 */
		public PageParameters getPageParameters()
		{
			return pageParameters;
		}
	}

	protected final List<MountPathSegment> pathSegments;

	protected final String[] mountSegments;

	protected final IPageParametersEncoder pageParametersEncoder;

	/**
	 * Construct.
	 */
	public AbstractBookmarkableMapper()
	{
		this("notUsed", new PageParametersEncoder());
	}

	public AbstractBookmarkableMapper(String mountPath, IPageParametersEncoder pageParametersEncoder)
	{
		Args.notEmpty(mountPath, "mountPath");

		this.pageParametersEncoder = Args.notNull(pageParametersEncoder, "pageParametersEncoder");
		mountSegments = getMountSegments(mountPath);
		pathSegments = getPathSegments(mountSegments);
	}

	/**
	 * Parse the given request to an {@link UrlInfo} instance.
	 * 
	 * @param request
	 * @return UrlInfo instance or <code>null</code> if this encoder can not handle the request
	 */
	protected abstract UrlInfo parseRequest(Request request);

	/**
	 * Builds URL for the given {@link UrlInfo} instance. The URL this method produces must be
	 * parseable by the {@link #parseRequest(Request)} method.
	 * 
	 * @param info
	 * @return Url result URL
	 */
	protected abstract Url buildUrl(UrlInfo info);

	/**
	 * Indicates whether hybrid {@link RenderPageRequestHandler} URL for page will be generated only
	 * if page has been created with bookmarkable URL.
	 * <p>
	 * For generic bookmarkable encoders this method should return <code>true</code>. For explicit
	 * (mounted) encoders this method should return <code>false</code>
	 * 
	 * @return <code>true</code> if hybrid URL requires page created bookmarkable,
	 *         <code>false</code> otherwise.
	 */
	protected abstract boolean pageMustHaveBeenCreatedBookmarkable();

	@Override
	public int getCompatibilityScore(Request request)
	{
		if (urlStartsWith(request.getUrl(), mountSegments))
		{
			/* see WICKET-5056 - alter score with pathSegment type */
			int countOptional = 0;
			int fixedSegments = 0;
			for (MountPathSegment pathSegment : pathSegments)
			{
				fixedSegments += pathSegment.getFixedPartSize();
				countOptional += pathSegment.getOptionalParameters();
			}
			return mountSegments.length - countOptional + fixedSegments;
		}
		else
		{
			return 0;
		}
	}

	/**
	 * Creates a {@code IRequestHandler} that processes a bookmarkable request.
	 * 
	 * @param pageClass
	 * @param pageParameters
	 * @return a {@code IRequestHandler} capable of processing the bookmarkable request.
	 */
	protected IRequestHandler processBookmarkable(Class<? extends IRequestablePage> pageClass,
		PageParameters pageParameters)
	{
		PageProvider provider = new PageProvider(pageClass, pageParameters);
		provider.setPageSource(getContext());
		return new RenderPageRequestHandler(provider);
	}

	/**
	 * Creates a {@code IRequestHandler} that processes a hybrid request. When the page identified
	 * by {@code pageInfo} was not available, the request should be treated as a bookmarkable
	 * request.
	 * 
	 * @param pageInfo
	 * @param pageClass
	 * @param pageParameters
	 * @param renderCount
	 * @return a {@code IRequestHandler} capable of processing the hybrid request.
	 */
	protected IRequestHandler processHybrid(PageInfo pageInfo,
		Class<? extends IRequestablePage> pageClass, PageParameters pageParameters,
		Integer renderCount)
	{
		PageProvider provider = new PageProvider(pageInfo.getPageId(), pageClass, pageParameters,
			renderCount);
		provider.setPageSource(getContext());

		checkExpiration(provider, pageInfo);

		/**
		 * https://issues.apache.org/jira/browse/WICKET-5734
		 * */
		PageParameters constructionPageParameters = provider.hasPageInstance() ?
			provider.getPageInstance().getPageParameters() : new PageParameters();

		if (PageParameters.equals(constructionPageParameters, pageParameters) == false)
		{
			// create a fresh page instance because the request page parameters are different than the ones
			// when the resolved page by id has been created
			return new RenderPageRequestHandler(new PageProvider(pageClass, pageParameters));
		}
		return new RenderPageRequestHandler(provider);
	}

	boolean getRecreateMountedPagesAfterExpiry()
	{
		return WebApplication.get().getPageSettings().getRecreateBookmarkablePagesAfterExpiry();
	}

	/**
	 * Creates a {@code IRequestHandler} that notifies an {@link IRequestListener}.
	 * 
	 * @param pageComponentInfo
	 * @param pageClass
	 * @param pageParameters
	 * @return a {@code IRequestHandler} that notifies an {@link IRequestListener}.
	 */
	protected IRequestHandler processListener(PageComponentInfo pageComponentInfo,
		Class<? extends IRequestablePage> pageClass, PageParameters pageParameters)
	{
		PageInfo pageInfo = pageComponentInfo.getPageInfo();
		ComponentInfo componentInfo = pageComponentInfo.getComponentInfo();
		Integer renderCount = null;

		if (componentInfo != null)
		{
			renderCount = componentInfo.getRenderCount();
		}

		PageAndComponentProvider provider = new PageAndComponentProvider(pageInfo.getPageId(),
			pageClass, pageParameters, renderCount, componentInfo.getComponentPath());

		provider.setPageSource(getContext());

		checkExpiration(provider, pageInfo);

		return new ListenerRequestHandler(provider, componentInfo.getBehaviorId());
	}

	private void checkExpiration(PageProvider provider, PageInfo pageInfo)
	{
		if (provider.wasExpired() && !getRecreateMountedPagesAfterExpiry())
		{
			throw new PageExpiredException(String.format("Bookmarkable page with id '%d' has expired.",
					pageInfo.getPageId()));
		}
	}

	@Override
	public IRequestHandler mapRequest(Request request)
	{
		UrlInfo urlInfo = parseRequest(request);

		if (urlInfo != null)
		{
			PageComponentInfo info = urlInfo.getPageComponentInfo();
			Class<? extends IRequestablePage> pageClass = urlInfo.getPageClass();
			PageParameters pageParameters = urlInfo.getPageParameters();

			if (info == null)
			{
				// if there are is no page instance information
				// then this is a simple bookmarkable URL
				return processBookmarkable(pageClass, pageParameters);
			}
			else if (info.getPageInfo().getPageId() != null && info.getComponentInfo() == null)
			{
				// if there is page instance information in the URL but no component and listener
				// interface then this is a hybrid URL - we need to try to reuse existing page
				// instance
				return processHybrid(info.getPageInfo(), pageClass, pageParameters, null);
			}
			else if (info.getComponentInfo() != null)
			{
				// with both page instance and component this is a request listener URL
				return processListener(info, pageClass, pageParameters);
			}
			else if (info.getPageInfo().getPageId() == null)
			{
				return processBookmarkable(pageClass, pageParameters);
			}

		}
		return null;
	}

	protected boolean checkPageInstance(IRequestablePage page)
	{
		return page != null && checkPageClass(page.getClass());
	}

	protected boolean checkPageClass(Class<? extends IRequestablePage> pageClass)
	{
		return true;
	}

	@Override
	public Url mapHandler(IRequestHandler requestHandler)
	{
		// TODO see if we can refactor this to remove dependency on instanceof checks below and
		// eliminate the need for IRequestHandlerDelegate
		while (requestHandler instanceof IRequestHandlerDelegate)
		{
			requestHandler = ((IRequestHandlerDelegate)requestHandler).getDelegateHandler();
		}

		if (requestHandler instanceof BookmarkablePageRequestHandler)
		{
			// simple bookmarkable URL with no page instance information
			BookmarkablePageRequestHandler handler = (BookmarkablePageRequestHandler)requestHandler;

			if (!checkPageClass(handler.getPageClass()))
			{
				return null;
			}

			PageInfo info = new PageInfo();
			UrlInfo urlInfo = new UrlInfo(new PageComponentInfo(info, null),
				handler.getPageClass(), handler.getPageParameters());

			return buildUrl(urlInfo);
		}
		else if (requestHandler instanceof RenderPageRequestHandler)
		{
			// possibly hybrid URL - bookmarkable URL with page instance information
			// but only allowed if the page was created by bookmarkable URL

			RenderPageRequestHandler handler = (RenderPageRequestHandler)requestHandler;

			if (!checkPageClass(handler.getPageClass()))
			{
				return null;
			}

			if (handler.getPageProvider().isNewPageInstance())
			{
				// no existing page instance available, don't bother creating new page instance
				PageInfo info = new PageInfo();
				UrlInfo urlInfo = new UrlInfo(new PageComponentInfo(info, null),
					handler.getPageClass(), handler.getPageParameters());

				return buildUrl(urlInfo);
			}

			IRequestablePage page = handler.getPage();

			if (checkPageInstance(page) &&
				(!pageMustHaveBeenCreatedBookmarkable() || page.wasCreatedBookmarkable()))
			{
				PageInfo info = getPageInfo(handler);
				PageComponentInfo pageComponentInfo = new PageComponentInfo(info, null);

				UrlInfo urlInfo = new UrlInfo(pageComponentInfo, page.getClass(),
					handler.getPageParameters());
				return buildUrl(urlInfo);
			}
			else
			{
				return null;
			}

		}
		else if (requestHandler instanceof BookmarkableListenerRequestHandler)
		{
			// request listener URL with page class information
			BookmarkableListenerRequestHandler handler = (BookmarkableListenerRequestHandler)requestHandler;
			Class<? extends IRequestablePage> pageClass = handler.getPageClass();

			if (!checkPageClass(pageClass))
			{
				return null;
			}

			Integer renderCount = null;
			if (handler.includeRenderCount())
			{
				renderCount = handler.getRenderCount();
			}

			PageInfo pageInfo = getPageInfo(handler);
			ComponentInfo componentInfo = new ComponentInfo(renderCount, handler.getComponentPath(), handler.getBehaviorIndex());

			PageParameters parameters = getRecreateMountedPagesAfterExpiry() ? new PageParameters(
				handler.getPage().getPageParameters()).mergeWith(handler.getPageParameters())
				: handler.getPageParameters();
			UrlInfo urlInfo = new UrlInfo(new PageComponentInfo(pageInfo, componentInfo),
				pageClass, parameters);
			return buildUrl(urlInfo);
		}

		return null;
	}

	protected final PageInfo getPageInfo(IPageRequestHandler handler)
	{
		Args.notNull(handler, "handler");

		Integer pageId = null;
		if (handler.isPageInstanceCreated())
		{
			IRequestablePage page = handler.getPage();

			if (page.isPageStateless() == false)
			{
				pageId = page.getPageId();
			}
		}

		return new PageInfo(pageId);
	}

	/**
	 * @return a new instance of {@link PageParameters} that will be passed to the page/resource
	 */
	protected PageParameters newPageParameters()
	{
		final PageParameters parameters = new PageParameters();
		parameters.setLocale(resolveLocale());
		return parameters;
	}

	/**
	 * Override {@link #resolveLocale()} to return the result of this method if you want to use
	 * the user's session or request locale for parsing numbers from the page parameters
	 *
	 * @return the Session or Request's locale to use for parsing any numbers in the request parameters
	 */
	protected Locale resolveUserLocale()
	{
		Locale locale = super.resolveLocale();
		if (Session.exists())
		{
			locale = Session.get().getLocale();
		}
		else
		{
			RequestCycle requestCycle = RequestCycle.get();
			if (requestCycle != null)
			{
				Request request = requestCycle.getRequest();
				if (request != null)
				{
					locale = request.getLocale();
				}
			}
		}

		return locale;
	}

	protected static class MountPathSegment
	{
		private int segmentIndex;
		private String fixedPart;
		private int minParameters;
		private int optionalParameters;

		public MountPathSegment(int segmentIndex)
		{
			this.segmentIndex = segmentIndex;
		}

		public void setFixedPart(String fixedPart)
		{
			this.fixedPart = fixedPart;
		}

		public void addRequiredParameter()
		{
			minParameters++;
		}

		public void addOptionalParameter()
		{
			optionalParameters++;
		}

		public int getSegmentIndex()
		{
			return segmentIndex;
		}

		public String getFixedPart()
		{
			return fixedPart;
		}

		public int getMinParameters()
		{
			return minParameters;
		}

		public int getOptionalParameters()
		{
			return optionalParameters;
		}

		public int getMaxParameters()
		{
			return getOptionalParameters() + getMinParameters();
		}

		public int getFixedPartSize()
		{
			return getFixedPart() == null ? 0 : 1;
		}

		@Override
		public String toString()
		{
			return "(" + getSegmentIndex() + ") " + getMinParameters() + '-' + getMaxParameters() +
					' ' + (getFixedPart() == null ? "(end)" : getFixedPart());
		}
	}

	protected List<MountPathSegment> getPathSegments(String[] segments)
	{
		List<MountPathSegment> ret = new ArrayList<MountPathSegment>();
		int segmentIndex = 0;
		MountPathSegment curPathSegment = new MountPathSegment(segmentIndex);
		ret.add(curPathSegment);
		for (String curSegment : segments)
		{
			if (isFixedSegment(curSegment))
			{
				curPathSegment.setFixedPart(curSegment);
				curPathSegment = new MountPathSegment(segmentIndex + 1);
				ret.add(curPathSegment);
			}
			else if (getPlaceholder(curSegment) != null)
			{
				curPathSegment.addRequiredParameter();
			}
			else
			{
				curPathSegment.addOptionalParameter();
			}
			segmentIndex++;
		}
		return ret;
	}

	protected boolean isFixedSegment(String segment)
	{
		return getOptionalPlaceholder(segment) == null && getPlaceholder(segment) == null;
	}


	/**
	 * Extracts the PageParameters from URL if there are any
	 */
	protected PageParameters extractPageParameters(Request request, Url url)
	{
		int[] matchedParameters = getMatchedSegmentSizes(url);
		
		int total = 0;
		for (int curMatchSize : matchedParameters)
		{
			total += curMatchSize;
		}
		PageParameters pageParameters = extractPageParameters(request, total, pageParametersEncoder);
		if (pageParameters != null)
		{
			pageParameters.setLocale(resolveLocale());
		}

		int segmentIndex = 0;
		for (int pathSegmentIndex = 0; pathSegmentIndex < pathSegments.size(); pathSegmentIndex++)
		{
			MountPathSegment pathSegment = pathSegments.get(pathSegmentIndex);
			
			int totalAdded = 0;
			int requiredAdded = 0;
			for (int segmentParameterIndex = 0; segmentParameterIndex < pathSegment.getMaxParameters() && totalAdded < matchedParameters[pathSegmentIndex]; segmentParameterIndex++)
			{
				if (pageParameters == null)
				{
					pageParameters = newPageParameters();
				}

				String curSegment = mountSegments[pathSegment.getSegmentIndex() + segmentParameterIndex];

				String placeholder = getPlaceholder(curSegment);
				String optionalPlaceholder = getOptionalPlaceholder(curSegment);
				// extract the parameter from URL
				if (placeholder != null)
				{
					pageParameters.add(placeholder,
							url.getSegments().get(segmentIndex), INamedParameters.Type.PATH);
					segmentIndex++;
					totalAdded++;
					requiredAdded++;
				}
				else if (optionalPlaceholder != null &&
					matchedParameters[pathSegmentIndex] - segmentParameterIndex > pathSegment.getMinParameters() + pathSegment.getFixedPartSize() - requiredAdded)
				{
					pageParameters.add(optionalPlaceholder,
							url.getSegments().get(segmentIndex), INamedParameters.Type.PATH);
					segmentIndex++;
					totalAdded++;
				}
			}

			segmentIndex += pathSegment.getFixedPartSize();
		}
		return pageParameters;
	}

	protected int[] getMatchedSegmentSizes(Url url)
	{
		int[] ret = new int[pathSegments.size()];
		int segmentIndex = 0;
		int pathSegmentIndex = 0;
		for (MountPathSegment curPathSegment : pathSegments.subList(0, pathSegments.size() - 1))
		{
			boolean foundFixedPart = false;
			segmentIndex += curPathSegment.getMinParameters();
			int max = Math.min(curPathSegment.getOptionalParameters() + 1,
					url.getSegments().size() - segmentIndex);

			for (int count = max - 1; count >= 0; count--)
			{
				if (segmentsMatch(url.getSegments()
						.get(segmentIndex + count), curPathSegment.getFixedPart()))
				{
					foundFixedPart = true;
					segmentIndex += count + 1;
					ret[pathSegmentIndex] = count + curPathSegment.getMinParameters() + 1;
					break;
				}
			}
			if (!foundFixedPart)
				return null;
			pathSegmentIndex++;
		}
		MountPathSegment lastSegment = pathSegments.get(pathSegments.size() - 1);
		segmentIndex += lastSegment.getMinParameters();
		if (segmentIndex > url.getSegments().size())
			return null;
		ret[pathSegmentIndex] = Math.min(lastSegment.getMaxParameters(), url.getSegments().size() -
				segmentIndex + lastSegment.getMinParameters());
		return ret;
	}

	/**
	 * Decides whether a segment from the mounted path matches with a segment
	 * from the requested url.
	 *
	 * A custom implementation of this class may use more complex logic to handle
	 * spelling errors
	 *
	 * @param mountedSegment
	 *          the segment from the mounted path
	 * @param urlSegment
	 *          the segment from the request url
	 * @return {@code true} if the segments match
	 */
	protected boolean segmentsMatch(String mountedSegment, String urlSegment)
	{
		final boolean result;
		if (isCaseSensitiveMatch())
		{
			result = mountedSegment.equals(urlSegment);
		}
		else
		{
			result = mountedSegment.equalsIgnoreCase(urlSegment);
		}
		return result;
	}

	/**
	 * @return whether the matching of mounted segments against request's url ones should be
	 *      case sensitive or not
	 */
	protected boolean isCaseSensitiveMatch()
	{
		return isCaseSensitive;
	}

	/**
	 * Sets whether the matching of mounted segments against request's url ones should be
	 * case sensitive or not.
	 *
	 * @param isCaseSensitive
	 *          a flag indicating whether the matching of mounted segments against request's
	 *          url ones should be case sensitive or not
	 * @return this instance, for chaining
	 */
	public AbstractBookmarkableMapper setCaseSensitiveMatch(boolean isCaseSensitive)
	{
		this.isCaseSensitive = isCaseSensitive;
		return this;
	}

	/**
	 * Replaces mandatory and optional parameters with their values.
	 *
	 * If a mandatory parameter is not provided then the method returns {@code false}
	 * indicating that there is a problem.
	 * Optional parameters with missing values are just dropped.
	 *
	 * @param parameters
	 *          The parameters with the values
	 * @param url
	 *          The url with the placeholders
	 * @return
	 *          {@code true} if all mandatory parameters are properly substituted,
	 *          {@code false} - otherwise
	 */
	protected boolean setPlaceholders(PageParameters parameters, Url url)
	{
		boolean mandatoryParametersSet = true;

		int dropped = 0;
		for (int i = 0; i < mountSegments.length; ++i)
		{
			String placeholder = getPlaceholder(mountSegments[i]);
			String optionalPlaceholder = getOptionalPlaceholder(mountSegments[i]);
			if (placeholder != null)
			{
				if (parameters.getNamedKeys().contains(placeholder))
				{
					url.getSegments().set(i - dropped, parameters.get(placeholder).toString());
					parameters.remove(placeholder);
				}
				else
				{
					mandatoryParametersSet = false;
					break;
				}
			}
			else if (optionalPlaceholder != null)
			{
				if (parameters.getNamedKeys().contains(optionalPlaceholder))
				{
					url.getSegments().set(i - dropped, parameters.get(optionalPlaceholder).toString(""));
					parameters.remove(optionalPlaceholder);
				}
				else
				{
					url.getSegments().remove(i - dropped);
					dropped++;
				}
			}
		}

		return mandatoryParametersSet;
	}
	
	protected boolean urlStartsWithMountedSegments(Url url)
	{
		if (url == null)
		{
			return false;
		}
		else
		{
			return getMatchedSegmentSizes(url) != null;
		}
	}
}
