/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * NavAddress.java * Creation date: Aug 5, 2003 * By: Frank Worsley */ package org.openquark.gems.client.navigator; import java.util.HashMap; import java.util.Map; import org.openquark.cal.compiler.ClassInstance; import org.openquark.cal.compiler.ClassInstanceIdentifier; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.compiler.ScopedEntity; import org.openquark.cal.services.CALFeatureName; import org.openquark.cal.services.GemEntity; import org.openquark.cal.services.MetaModule; import org.openquark.cal.services.FeatureName.FeatureType; import org.openquark.gems.client.CollectorGem; /** * This class implements the addresses that are used within the CAL navigator to uniquely * identify any location within the CAL workspace that the navigator can display. Not every * location corresponds to a CAL language entity, addresses also encode 'virtual' locations * such as the search results page or a module vault. * <br><br> * This class also provides helper methods to work with the metadata that may be associated * with a location. It allows a client to save/load metadata for a location and correctly * determine the display name for a location using the metadata if applicable. * <br><br> * Note that this class is immutable. * <br><br> * A navigator address is similiar to a URL. It can be expressed in string form and consists * of a method, base, parameters and an anchor. The method identifies the type of entity the * address is for. The base is the unique name that can be used to resolve the entity. * The parameters are a set of name-value pairs for any additional information that is attached * to an address. The anchor part is used to specify a location on the HTML page that is * associated with the address. HTML pages for an address are generated by the NavHtmlFactory. * <br><br> * The string form of a url looks as follows: * <br><br> * function://List.map&argument=1#relatedFeatures * <br><br> * Method: function<br> * Base: List.map<br> * Parameters: argument=1<br> * Anchor: relatedFeatures<br> * <br><br> * This url identifies the function List.map in the Perspective. There is one parameter with * the name 'argument' and value '1'. The anchor for the address is "relatedFeatures". * * @author Frank Worsley */ public final class NavAddress { /** * The class for the address method enum pattern. * @author Frank Worsley */ public static final class NavAddressMethod { private final String method; private NavAddressMethod(String method) { if (method == null) { throw new NullPointerException(); } this.method = method; } @Override public String toString() { return method; } } /* Methods for addresses. */ /** The method of addresses that point to functions. */ public static final NavAddressMethod FUNCTION_METHOD = new NavAddressMethod("function"); /** The method of addresses that point to method constructors. */ public static final NavAddressMethod TYPE_CONSTRUCTOR_METHOD = new NavAddressMethod("typeConstructor"); /** The method of addresses that point to data constructors. */ public static final NavAddressMethod DATA_CONSTRUCTOR_METHOD = new NavAddressMethod("dataConstructor"); /** The method of addresses that point to class methods. */ public static final NavAddressMethod CLASS_METHOD_METHOD = new NavAddressMethod("classMethod"); /** The method of addresses that point to method classes. */ public static final NavAddressMethod TYPE_CLASS_METHOD = new NavAddressMethod("typeClass"); /** The method of addresses that point to class instances. */ public static final NavAddressMethod CLASS_INSTANCE_METHOD = new NavAddressMethod("classInstance"); /** The method of addresses that point to instance methods. */ public static final NavAddressMethod INSTANCE_METHOD_METHOD = new NavAddressMethod("instanceMethod"); /** The method of addresses that point to modules. */ public static final NavAddressMethod MODULE_METHOD = new NavAddressMethod("module"); /** The method of addresses that point to module namespaces. */ public static final NavAddressMethod MODULE_NAMESPACE_METHOD = new NavAddressMethod("moduleNamespace"); /** The method of addresses that point to the root of the workspace. */ public static final NavAddressMethod WORKSPACE_METHOD = new NavAddressMethod("workspace"); /** The method of addresses that point to search results. */ public static final NavAddressMethod SEARCH_METHOD = new NavAddressMethod("search"); /** The method of addresses that point to collectors. */ public static final NavAddressMethod COLLECTOR_METHOD = new NavAddressMethod("collector"); /* Parameter names. */ /** The parameter that identifies what vault to display for modules. */ public static final String VAULT_PARAMETER = "vault"; /** The parameter that identifies the argument number for gem entities. */ public static final String ARGUMENT_PARAMETER = "argument"; /** The parameter that identifies the type class for class instances. */ public static final String INSTANCE_CLASS_PARAMETER = "instanceTypeClass"; /** The parameter that identifies the type constructor of class instances. */ public static final String INSTANCE_TYPE_PARAMETER = "instanceTypeConstructor"; /** The parameter that identifies the module a class instance is declared in. */ public static final String INSTANCE_MODULE_PARAMETER = "instanceModule"; /** The parameter that identifies the method name of instance methods. */ public static final String INSTANCE_METHOD_PARAMETER = "instanceMethod"; /* Parameter values. */ /** The parameter value for the type vault. */ public static final String TYPE_VAULT_VALUE = "TypeVault"; /** The parameter value for the class vault. */ public static final String CLASS_VAULT_VALUE = "ClassVault"; /** The parameter value for the instance vault. */ public static final String INSTANCE_VAULT_VALUE = "InstanceVault"; /** The parameter value for the function vault. */ public static final String FUNCTION_VAULT_VALUE = "FunctionVault"; /* Special characters. */ /** The special string used to separate the method from the base. */ public static final String METHOD_SEPARATOR = "://"; /** The special character used to separate the anchor from the base. */ public static final String ANCHOR_SEPARATOR = "#"; /** The special character used to separate parameters. */ public static final String PARAMETER_SEPARATOR = "&"; /** The special charater used to separate values from their parameter name. */ public static final String VALUE_SEPARATOR = "="; /* Instance Fields. */ /** The method of the address. This field is never null. */ private final NavAddressMethod method; /** The base of the address or null if no base. */ private final String base; /** The anchor of the address or null if no anchor. */ private final String anchor; /** The parameters included in the address. This field is never null. */ private final Map<String, String> parameters; /** * Private constructor for a new address with the given information. * Use the public static factory methods to get an instance of an address. * * @param method the method of the address * @param base the base of the address * @param anchor the anchor of the address * @param parameters the parameters for the address */ private NavAddress(NavAddressMethod method, String base, String anchor, Map<String, String> parameters) { if (method == null) { throw new NullPointerException(); } this.method = method; this.base = base; this.anchor = anchor; this.parameters = parameters != null ? parameters : new HashMap<String, String>(); } /** * @param featureName the feature name to get an address for * @return an address for the given feature name */ public static NavAddress getAddress(CALFeatureName featureName) { FeatureType type = featureName.getType(); if (type == CALFeatureName.FUNCTION) { return new NavAddress(FUNCTION_METHOD, featureName.getName(), null, null); } else if (type == CALFeatureName.TYPE_CONSTRUCTOR) { return new NavAddress(TYPE_CONSTRUCTOR_METHOD, featureName.getName(), null, null); } else if (type == CALFeatureName.TYPE_CLASS) { return new NavAddress(TYPE_CLASS_METHOD, featureName.getName(), null, null); } else if (type == CALFeatureName.DATA_CONSTRUCTOR) { return new NavAddress(DATA_CONSTRUCTOR_METHOD, featureName.getName(), null, null); } else if (type == CALFeatureName.CLASS_METHOD) { return new NavAddress(CLASS_METHOD_METHOD, featureName.getName(), null, null); } else if (type == CALFeatureName.MODULE) { return new NavAddress(MODULE_METHOD, featureName.getName(), null, null); } else if (type == CALFeatureName.CLASS_INSTANCE) { ClassInstanceIdentifier identifier = featureName.toInstanceIdentifier(); ModuleName moduleName = featureName.toModuleName(); Map<String, String> parameters = new HashMap<String, String>(); parameters.put(INSTANCE_CLASS_PARAMETER, identifier.getTypeClassName().getQualifiedName()); parameters.put(INSTANCE_TYPE_PARAMETER, identifier.getTypeIdentifier()); parameters.put(INSTANCE_MODULE_PARAMETER, moduleName.toSourceText()); return new NavAddress(CLASS_INSTANCE_METHOD, null, null, parameters); } else if (type == CALFeatureName.INSTANCE_METHOD) { ClassInstanceIdentifier identifier = featureName.toInstanceIdentifier(); ModuleName moduleName = featureName.toModuleName(); String methodName = featureName.toInstanceMethodName(); Map<String, String> parameters = new HashMap<String, String>(); parameters.put(INSTANCE_CLASS_PARAMETER, identifier.getTypeClassName().getQualifiedName()); parameters.put(INSTANCE_TYPE_PARAMETER, identifier.getTypeIdentifier()); parameters.put(INSTANCE_MODULE_PARAMETER, moduleName.toSourceText()); parameters.put(INSTANCE_METHOD_PARAMETER, methodName); return new NavAddress(INSTANCE_METHOD_METHOD, null, null, parameters); } throw new IllegalArgumentException("feature type not supported: " + type); } /** * @param module the module to get an address for * @return the address for the given module */ public static NavAddress getAddress(MetaModule module) { return new NavAddress(MODULE_METHOD, module.getName().toSourceText(), null, null); } /** * @param moduleInfo the method info of the module to get an address for * @return the address for the module of the given method info */ public static NavAddress getAddress(ModuleTypeInfo moduleInfo) { return new NavAddress(MODULE_METHOD, moduleInfo.getModuleName().toSourceText(), null, null); } /** * @param moduleName the module name to get an address for * @return the address for the module of the given method info */ public static NavAddress getModuleNamespaceAddress(final ModuleName moduleName) { return new NavAddress(MODULE_NAMESPACE_METHOD, moduleName.toSourceText(), null, null); } /** * @param entity the entity to get an address for * @return the address for the given entity */ public static NavAddress getAddress(GemEntity entity) { return getAddress(entity.getFunctionalAgent()); } /** * @param entity the entity to get an address for * @return the address for the given entity */ public static NavAddress getAddress(ScopedEntity entity) { return getAddress(CALFeatureName.getScopedEntityFeatureName(entity)); } /** * @param classInstance the class instance to get an address for * @return the address for the given class instance */ public static NavAddress getAddress(ClassInstance classInstance) { Map<String, String> parameters = new HashMap<String, String>(); parameters.put(INSTANCE_CLASS_PARAMETER, classInstance.getTypeClass().getName().getQualifiedName()); parameters.put(INSTANCE_TYPE_PARAMETER, classInstance.getIdentifier().getTypeIdentifier()); parameters.put(INSTANCE_MODULE_PARAMETER, classInstance.getModuleName().toSourceText()); return new NavAddress(CLASS_INSTANCE_METHOD, null, null, parameters); } /** * @param collector the collector to get an address for * @return the address for the given collector */ public static NavAddress getAddress(CollectorGem collector) { return new NavAddress(COLLECTOR_METHOD, collector.getUnqualifiedName(), null, null); } /** * @param searchString the search string being searched for * @return the address for the given search result page */ public static NavAddress getSearchAddress(String searchString) { return new NavAddress(SEARCH_METHOD, searchString, null, null); } /** * Parses the given string and returns a corresponding address object. * @param address the string to parse * @return the address object corresponding to the string address * @throws IllegalArgumentException if the string does not represent a valid address */ public static NavAddress getAddress(String address) { int i = address.lastIndexOf(METHOD_SEPARATOR); int a = address.lastIndexOf(ANCHOR_SEPARATOR); String method = address.substring(0, i); String base = (a != -1) ? address.substring(i + 3, a) : address.substring(i + 3); String anchor = (a == -1) ? null : address.substring(a + 1); Map<String, String> parameters = null; if (base.trim().length() == 0) { base = null; } if (base != null && base.indexOf(PARAMETER_SEPARATOR) != -1) { parameters = new HashMap<String, String>(); int p = -1; while ((p = base.indexOf(PARAMETER_SEPARATOR, p + 1)) != -1) { int c = base.indexOf(VALUE_SEPARATOR, p); int d = base.indexOf(PARAMETER_SEPARATOR, c); String name = base.substring(p + 1, c); String value = base.substring(c + 1, d != -1 ? d : base.length()); parameters.put(name, value); } p = base.indexOf(PARAMETER_SEPARATOR); base = base.substring(0, p); } return new NavAddress(stringToMethod(method), base, anchor, parameters); } /** * Checks if we know about a method that matches that string and returns * that method, if any. * @param methodString the string to find a method for * @return the method for the given string * @throws IllegalArgumentException if the string doesn't match any method */ private static NavAddressMethod stringToMethod(String methodString) { if (methodString.equals(FUNCTION_METHOD.toString())) { return FUNCTION_METHOD; } else if (methodString.equals(TYPE_CONSTRUCTOR_METHOD.toString())) { return TYPE_CONSTRUCTOR_METHOD; } else if (methodString.equals(DATA_CONSTRUCTOR_METHOD.toString())) { return DATA_CONSTRUCTOR_METHOD; } else if (methodString.equals(CLASS_METHOD_METHOD.toString())) { return CLASS_METHOD_METHOD; } else if (methodString.equals(TYPE_CLASS_METHOD.toString())) { return TYPE_CLASS_METHOD; } else if (methodString.equals(CLASS_INSTANCE_METHOD.toString())) { return CLASS_INSTANCE_METHOD; } else if (methodString.equals(INSTANCE_METHOD_METHOD.toString())) { return INSTANCE_METHOD_METHOD; } else if (methodString.equals(MODULE_METHOD.toString())) { return MODULE_METHOD; } else if (methodString.equals(MODULE_NAMESPACE_METHOD.toString())) { return MODULE_NAMESPACE_METHOD; } else if (methodString.equals(WORKSPACE_METHOD.toString())) { return WORKSPACE_METHOD; } else if (methodString.equals(SEARCH_METHOD.toString())) { return SEARCH_METHOD; } else if (methodString.equals(COLLECTOR_METHOD.toString())) { return COLLECTOR_METHOD; } throw new IllegalArgumentException("method not supported: " + methodString); } /** * @param method the method to get a root address for * @return the address object for the root with the given method */ public static NavAddress getRootAddress(NavAddressMethod method) { return new NavAddress(method, null, null, null); } /** * @param method the method to convert to * @return converts this address to a new address object with the given method */ public NavAddress withMethod(NavAddressMethod method) { return new NavAddress(method, base, anchor, parameters); } /** * @param anchor the anchor to convert to * @return convert this address to a new address object with the given anchor */ public NavAddress withAnchor(String anchor) { return new NavAddress(method, base, anchor, parameters); } /** * @param name the name of the parameter to add * @param value the value of the parameter to add * @return a new address object with the given parameter added * @throws IllegalArgumentException if the parameter name or value are invalid */ public NavAddress withParameter(String name, String value) { if (value.indexOf(PARAMETER_SEPARATOR) != -1 || name.indexOf(PARAMETER_SEPARATOR) != -1) { throw new IllegalArgumentException("parameter names and values cannot contain the separator string: " + PARAMETER_SEPARATOR); } if (value.indexOf(VALUE_SEPARATOR) != -1 || name.indexOf(VALUE_SEPARATOR) != -1) { throw new IllegalArgumentException("parameters names and values cannot contain the separator string: " + VALUE_SEPARATOR); } Map<String, String> parameters = new HashMap<String, String>(this.parameters); parameters.put(name, value); return new NavAddress(method, base, anchor, parameters); } /** * @return returns a new address object without the anchor part */ public NavAddress withAnchorStripped() { return new NavAddress(method, base, null, parameters); } /** * @return returns a new address object with anchor and parameters removed */ public NavAddress withAllStripped() { return new NavAddress(method, base, null, null); } /** * @return the anchor of the address */ public String getAnchor() { return anchor; } /** * @return the method of the address */ public NavAddressMethod getMethod() { return method; } /** * @return the base of the address */ public String getBase() { return base; } /** * @param name the name of the parameter * @return the value of the parameter with the given name or null if no such parameter */ public String getParameter(String name) { return parameters.get(name); } /** * Converts this address to a feature name, if possible. * @return a CALFeatureName for the entity this address is for * @throws UnsupportedOperationException of this address cannot be converted to a feature name */ public CALFeatureName toFeatureName() { if (method == FUNCTION_METHOD) { return CALFeatureName.getFunctionFeatureName(QualifiedName.makeFromCompoundName(base)); } else if (method == CLASS_METHOD_METHOD) { return CALFeatureName.getClassMethodFeatureName(QualifiedName.makeFromCompoundName(base)); } else if (method == TYPE_CLASS_METHOD) { return CALFeatureName.getTypeClassFeatureName(QualifiedName.makeFromCompoundName(base)); } else if (method == TYPE_CONSTRUCTOR_METHOD) { return CALFeatureName.getTypeConstructorFeatureName(QualifiedName.makeFromCompoundName(base)); } else if (method == DATA_CONSTRUCTOR_METHOD) { return CALFeatureName.getDataConstructorFeatureName(QualifiedName.makeFromCompoundName(base)); } else if (method == MODULE_METHOD) { return CALFeatureName.getModuleFeatureName(ModuleName.make(base)); } else if (method == MODULE_NAMESPACE_METHOD) { return CALFeatureName.getModuleFeatureName(ModuleName.make(base)); } else if (method == CLASS_INSTANCE_METHOD) { ModuleName moduleName = ModuleName.make(getParameter(INSTANCE_MODULE_PARAMETER)); QualifiedName className = QualifiedName.makeFromCompoundName(getParameter(INSTANCE_CLASS_PARAMETER)); String typeIdentifier = getParameter(INSTANCE_TYPE_PARAMETER); ClassInstanceIdentifier identifier = ClassInstanceIdentifier.make(className, typeIdentifier); return CALFeatureName.getClassInstanceFeatureName(identifier, moduleName); } else if (method == INSTANCE_METHOD_METHOD) { ModuleName moduleName = ModuleName.make(getParameter(INSTANCE_MODULE_PARAMETER)); QualifiedName className = QualifiedName.makeFromCompoundName(getParameter(INSTANCE_CLASS_PARAMETER)); String typeIdentifier = getParameter(INSTANCE_TYPE_PARAMETER); String methodName = getParameter(INSTANCE_METHOD_PARAMETER); ClassInstanceIdentifier identifier = ClassInstanceIdentifier.make(className, typeIdentifier); return CALFeatureName.getInstanceMethodFeatureName(identifier, moduleName, methodName); } throw new UnsupportedOperationException("this type of address cannot be converted to a feature name"); } /** * @return the string form of the address * @see java.lang.Object#toString() */ @Override public String toString() { return method + METHOD_SEPARATOR + (base != null ? base : "") + getParametersString() + (anchor != null ? ANCHOR_SEPARATOR + anchor : ""); } /** * @return the string representation of the parameters in the form &foo=value&bar=baz */ private String getParametersString() { if (parameters.size() == 0) { return ""; } StringBuilder builder = new StringBuilder(); for (final Map.Entry<String, String> entry : parameters.entrySet()) { builder.append(PARAMETER_SEPARATOR); String name = entry.getKey(); String value = entry.getValue(); builder.append(name + VALUE_SEPARATOR + value); } return builder.toString(); } /** * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object o) { if (o instanceof NavAddress) { return o.toString().equals(toString()); } return false; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return toString().hashCode(); } }