package er.extensions.components;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import com.webobjects.appserver.WOApplication;
import com.webobjects.appserver.WOAssociation;
import com.webobjects.appserver.WOComponent;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WOElement;
import com.webobjects.appserver.WOResourceManager;
import com.webobjects.appserver.WOResponse;
import com.webobjects.appserver._private.WOConstantValueAssociation;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSBundle;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSForwardException;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import er.extensions.appserver.ERXApplication;
import er.extensions.appserver.ERXWOContext;
import er.extensions.foundation.ERXStringUtilities;
import er.extensions.foundation.ERXValueUtilities;
/**
* ERXComponentUtilities contains WOComponent/WOElement-related utility methods.
*
* @author mschrag
*/
public class ERXComponentUtilities {
// use these so you don't need to check for null
public static WOAssociation TRUE = new WOConstantValueAssociation(Boolean.TRUE);
public static WOAssociation FALSE = new WOConstantValueAssociation(Boolean.FALSE);
public static WOAssociation EMPTY = new WOConstantValueAssociation("");
public static WOAssociation ZERO = new WOConstantValueAssociation(0);
/**
* Returns a query parameter dictionary from a set of ?key=association
* WOAssociation dictionary.
*
* @param associations
* the set of associations
* @param component
* the component to evaluate their values within
* @return a dictionary of key-value query parameters
*/
public static NSMutableDictionary queryParametersInComponent(NSDictionary associations, WOComponent component) {
NSMutableDictionary queryParameterAssociations = ERXComponentUtilities.queryParameterAssociations(associations);
return _queryParametersInComponent(queryParameterAssociations, component);
}
/**
* Returns a query parameter dictionary from a set of ?key=association
* WOAssociation dictionary.
*
* @param associations
* the set of associations
* @param component
* the component to evaluate their values within
* @param removeQueryParametersAssociations
* should the entries be removed from the passed-in dictionary?
* @return a dictionary of key-value query parameters
*/
public static NSMutableDictionary queryParametersInComponent(NSMutableDictionary associations, WOComponent component, boolean removeQueryParametersAssociations) {
NSMutableDictionary queryParameterAssociations = ERXComponentUtilities.queryParameterAssociations(associations, removeQueryParametersAssociations);
return _queryParametersInComponent(queryParameterAssociations, component);
}
public static NSMutableDictionary _queryParametersInComponent(NSMutableDictionary associations, WOComponent component) {
NSMutableDictionary queryParameters = new NSMutableDictionary();
Enumeration keyEnum = associations.keyEnumerator();
while (keyEnum.hasMoreElements()) {
String key = (String) keyEnum.nextElement();
WOAssociation association = (WOAssociation) associations.valueForKey(key);
Object associationValue = association.valueInComponent(component);
if (associationValue != null) {
queryParameters.setObjectForKey(associationValue, key.substring(1));
}
}
return queryParameters;
}
/**
* Returns the set of ?key=value associations from an associations
* dictionary.
*
* @param associations
* the associations to enumerate
* @return dictionary with query parameter associations
*/
public static NSMutableDictionary<String, WOAssociation> queryParameterAssociations(NSDictionary<String, WOAssociation> associations) {
return ERXComponentUtilities._queryParameterAssociations(associations, false);
}
/**
* Returns the set of ?key=value associations from an associations
* dictionary. If removeQueryParameterAssociations is <code>true</code>, the
* corresponding entries will be removed from the associations dictionary
* that was passed in.
*
* @param associations
* the associations to enumerate
* @param removeQueryParameterAssociations
* should the entries be removed from the passed-in dictionary?
* @return dictionary with query parameter associations
*/
public static NSMutableDictionary<String, WOAssociation> queryParameterAssociations(NSMutableDictionary<String, WOAssociation> associations, boolean removeQueryParameterAssociations) {
return ERXComponentUtilities._queryParameterAssociations(associations, removeQueryParameterAssociations);
}
public static NSMutableDictionary<String, WOAssociation> _queryParameterAssociations(NSDictionary<String, WOAssociation> associations, boolean removeQueryParameterAssociations) {
NSMutableDictionary<String, WOAssociation> mutableAssociations = null;
if (removeQueryParameterAssociations) {
mutableAssociations = (NSMutableDictionary) associations;
}
NSMutableDictionary<String, WOAssociation> queryParameterAssociations = new NSMutableDictionary<>();
Enumeration keyEnum = associations.keyEnumerator();
while (keyEnum.hasMoreElements()) {
String key = (String) keyEnum.nextElement();
if (key.startsWith("?")) {
WOAssociation association = (WOAssociation) associations.valueForKey(key);
if (mutableAssociations != null) {
mutableAssociations.removeObjectForKey(key);
}
queryParameterAssociations.setObjectForKey(association, key);
}
}
return queryParameterAssociations;
}
/**
* Returns the boolean value of a binding.
*
* @param component
* the component
* @param bindingName
* the name of the boolean binding
* @return a boolean
*/
public static boolean booleanValueForBinding(WOComponent component, String bindingName) {
return ERXComponentUtilities.booleanValueForBinding(component, bindingName, false);
}
/**
* Returns the boolean value of a binding.
*
* @param component
* the component
* @param bindingName
* the name of the boolean binding
* @param defaultValue
* the default value if the binding is null
* @return a boolean
*/
public static boolean booleanValueForBinding(WOComponent component, String bindingName, boolean defaultValue) {
if(component == null) {
return defaultValue;
}
return ERXValueUtilities.booleanValueWithDefault(component.valueForBinding(bindingName), defaultValue);
}
/**
* Returns the URL of the html template for the given component name.
*
* @param componentName
* the name of the component to load a template for (without the
* .wo)
* @param languages
* the list of languages to use for finding components
* @return the URL to the html template (or null if there isn't one)
*/
public static URL htmlTemplateUrl(String componentName, NSArray languages) {
return ERXComponentUtilities.templateUrl(componentName, "html", languages);
}
/**
* Returns the URL of the template for the given component name.
*
* @param componentName
* the name of the component to load a template for (without the
* .wo)
* @param extension
* the file extension of the template (without the dot -- i.e.
* "html")
* @param languages
* the list of languages to use for finding components
* @return the URL to the template (or null if there isn't one)
*/
public static URL templateUrl(String componentName, String extension, NSArray languages) {
String htmlPathName = componentName + ".wo/" + componentName + "." + extension;
WOResourceManager resourceManager = WOApplication.application().resourceManager();
URL templateUrl = pathUrlForResourceNamed(resourceManager, htmlPathName, languages);
if (templateUrl == null) {
// jw: hack for bundle-less builds as there is some sort of classpath problem that will
// pick up the wrong class for WOProjectBundle, _WOProject, … that register only component's
// .wo directories but not the containing files (.html, .wod, .woo). Thus we are assuming
// that if we can find the .wo directory we can manually point to the correct subfile
templateUrl = pathUrlForResourceNamed(resourceManager, componentName + ".wo", languages);
if (templateUrl != null) {
File templateDir = null;
try {
templateDir = new File(templateUrl.toURI());
} catch(URISyntaxException e) {
templateDir = new File(templateUrl.getPath());
}
if (templateDir.isDirectory()) {
File templateFile = new File(templateDir, componentName + "." + extension);
if (templateFile.exists()) {
try {
templateUrl = templateFile.toURI().toURL();
}
catch (MalformedURLException e) {
// ignore
}
}
}
}
}
return templateUrl;
}
private static URL pathUrlForResourceNamed(WOResourceManager resourceManager, String resourceName, NSArray languages) {
URL templateUrl = resourceManager.pathURLForResourceNamed(resourceName, null, languages);
if (templateUrl == null) {
NSArray frameworkBundles = NSBundle.frameworkBundles();
if (frameworkBundles != null) {
Enumeration frameworksEnum = frameworkBundles.objectEnumerator();
while (templateUrl == null && frameworksEnum.hasMoreElements()) {
NSBundle frameworkBundle = (NSBundle) frameworksEnum.nextElement();
templateUrl = resourceManager.pathURLForResourceNamed(resourceName, frameworkBundle.name(), languages);
}
}
}
return templateUrl;
}
/**
* Returns the contents of the html template for the given component name as
* a string.
*
* @param componentName
* the name of the component to load a template for (without the
* .wo)
* @param languages
* the list of languages to use for finding components
* @return the string contents of the html template (or null if there isn't
* one)
* @throws IOException
*/
public static String htmlTemplate(String componentName, NSArray languages) throws IOException {
return ERXComponentUtilities.template(componentName, "html", languages);
}
/**
* Returns the contents of the template for the given component name as a
* string.
*
* @param componentName
* the name of the component to load a template for (without the
* .wo)
* @param extension
* the file extension of the template (without the dot -- i.e.
* "html")
* @param languages
* the list of languages to use for finding components
* @return the string contents of the template (or null if there isn't one)
* @throws IOException
*/
public static String template(String componentName, String extension, NSArray languages) throws IOException {
String template;
URL templateUrl = ERXComponentUtilities.templateUrl(componentName, extension, languages);
if (templateUrl == null) {
template = null;
}
else {
template = ERXStringUtilities.stringFromURL(templateUrl);
}
return template;
}
/**
* Allows a component to "inherit" the template (.html and .wod files) from another component.
* <p>Usage in your WOComponent subclass:</p>
* <pre>
* @Override
* public WOElement template() {
* return ERXComponentUtilities.inheritTemplateFrom("AddAddress", session().languages());
* }
* </pre>
* This very simple implementation does have some limitations:
* <ol>
* <li>It can't be used to inherit the template of another component inheriting a template.</li>
* <li>It can't handle having two components with the same name in different packages or frameworks</li>
* <li>It does not use WO template caching</li>
* </ol>
*
* @see com.webobjects.appserver.WOComponent#template()
*
* @param componentName the name of the component whose template will be inherited
* @param languages the list of languages to use for finding components
* @return the template from the indicated component
*/
public static WOElement inheritTemplateFrom(String componentName, NSArray<String> languages) {
try {
/** require [valid_componentName] componentName != null; **/
String htmlString = ERXComponentUtilities.template(componentName, "html", languages);
String wodString = ERXComponentUtilities.template(componentName, "wod", languages);
return WOComponent.templateWithHTMLString("", "", htmlString, wodString, languages,
WOApplication.application().associationFactoryRegistry(), WOApplication.application().namespaceProvider());
/** ensure [valid_Result] Result != null; **/
}
catch (IOException e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
}
}
/**
* Returns an array of the current component names.
*
* @return array of current component names
*/
public static NSArray<String> componentTree() {
WOContext context = ERXWOContext.currentContext();
NSMutableArray<String> result = new NSMutableArray<>();
if (context != null) {
WOComponent c = context.component();
while (c != null) {
result.addObject(c.name());
c = c.parent();
}
}
return result;
}
/**
* Appends a dictionary of associations as HTML attributes.
*
* @param associations
* the associations dictionary
* @param response
* the response to write to
* @param context
* the context
*/
public static void appendHtmlAttributes(NSDictionary<String, WOAssociation> associations, WOResponse response, WOContext context) {
WOComponent component = context.component();
ERXComponentUtilities.appendHtmlAttributes(associations, response, component);
}
/**
* Appends a dictionary of associations as HTML attributes.
*
* @param associations
* the associations dictionary
* @param response
* the response to write to
* @param component
* the component to evaluate the associations within
*/
public static void appendHtmlAttributes(NSDictionary<String, WOAssociation> associations, WOResponse response, WOComponent component) {
for (String key : associations.allKeys()) {
WOAssociation association = associations.objectForKey(key);
ERXComponentUtilities.appendHtmlAttribute(key, association, response, component);
}
}
/**
* Appends a dictionary of associations as HTML attributes.
*
* @param associations
* the associations dictionary
* @param excludeKeys
* the associations to ignore
* @param response
* the response to write to
* @param component
* the component to evaluate the associations within
*/
public static void appendHtmlAttributes(NSDictionary<String, WOAssociation> associations, NSArray<String> excludeKeys, WOResponse response, WOComponent component) {
if (excludeKeys == null) {
excludeKeys = NSArray.EmptyArray;
}
for (String key : associations.allKeys()) {
if (!excludeKeys.contains(key)) {
WOAssociation association = associations.objectForKey(key);
ERXComponentUtilities.appendHtmlAttribute(key, association, response, component);
}
}
}
/**
* Appends an association as an HTML attribute.
*
* @param key
* the key to append
* @param association
* the association
* @param response
* the response to write to
* @param component
* the component to evaluate the association within
*/
public static void appendHtmlAttribute(String key, WOAssociation association, WOResponse response, WOComponent component) {
Object value = association.valueInComponent(component);
if (value != null) {
response.appendContentString(" ");
response.appendContentString(key);
response.appendContentString("=\"");
response.appendContentHTMLAttributeValue(value.toString());
response.appendContentString("\"");
}
}
/**
* Returns the component for the given class without having to cast. For
* example: MyPage page = ERXComponentUtilities.pageWithName(MyPage.class,
* context);
*
* @param <T>
* the type of component to
* @param componentClass
* the component class to lookup
* @param context
* the context
* @return the created component
*/
@SuppressWarnings("unchecked")
public static <T extends WOComponent> T pageWithName(Class<T> componentClass, WOContext context) {
return ERXApplication.erxApplication().pageWithName(componentClass, context);
}
/**
* Calls pageWithName with ERXWOContext.currentContext() for the current
* thread.
*
* @param <T>
* the type of component to
* @param componentClass
* the component class to lookup
* @return the created component
*/
@SuppressWarnings("unchecked")
public static <T extends WOComponent> T pageWithName(Class<T> componentClass) {
return ERXApplication.erxApplication().pageWithName(componentClass);
}
/**
* Checks if there is an association for a binding with the given name.
*
* @param name binding name
* @param associations array of associations
* @return <code>true</code> if the association exists
*/
public static boolean hasBinding(String name, NSDictionary<String, WOAssociation> associations) {
return associations.objectForKey(name) != null;
}
/**
* Returns the association for a binding with the given name. If there is
* no such association <code>null</code> will be returned.
*
* @param name binding name
* @param associations array of associations
* @return association for given binding or <code>null</code>
*/
public static WOAssociation bindingNamed(String name, NSDictionary<String, WOAssociation> associations) {
return associations.objectForKey(name);
}
/**
* Checks if the association for a binding with the given name can assign
* values at runtime.
*
* @param name binding name
* @param associations array of associations
* @return <code>true</code> if binding is settable
*/
public static boolean bindingIsSettable(String name, NSDictionary<String, WOAssociation> associations) {
boolean isSettable = false;
WOAssociation association = bindingNamed(name, associations);
if (association != null) {
isSettable = association.isValueSettable();
}
return isSettable;
}
/**
* Will try to set the given binding in the component to the passed value.
*
* @param value new value for the binding
* @param name binding name
* @param associations array of associations
* @param component component to set the value in
*/
public static void setValueForBinding(Object value, String name, NSDictionary<String, WOAssociation> associations, WOComponent component) {
WOAssociation association = bindingNamed(name, associations);
if (association != null) {
association.setValue(value, component);
}
}
/**
* Retrieves the current value of the given binding from the component. If there
* is no such binding or its value evaluates to <code>null</code> the default
* value will be returned.
*
* @param name binding name
* @param defaultValue default value
* @param associations array of associations
* @param component component to get value from
* @return retrieved value or default value
*/
public static Object valueForBinding(String name, Object defaultValue, NSDictionary<String, WOAssociation> associations, WOComponent component) {
Object value = valueForBinding(name, associations, component);
if (value != null) {
return value;
}
return defaultValue;
}
/**
* Retrieves the current value of the given binding from the component. If there
* is no such binding <code>null</code> will be returned.
*
* @param name binding name
* @param associations array of associations
* @param component component to get value from
* @return retrieved value or <code>null</code>
*/
public static Object valueForBinding(String name, NSDictionary<String, WOAssociation> associations, WOComponent component) {
WOAssociation association = bindingNamed(name, associations);
if (association != null) {
return association.valueInComponent(component);
}
return null;
}
/**
* Retrieves the current string value of the given binding from the component. If there
* is no such binding or its value evaluates to <code>null</code> the default
* value will be returned.
*
* @param name binding name
* @param defaultValue default value
* @param associations array of associations
* @param component component to get value from
* @return retrieved string value or default value
*/
public static String stringValueForBinding(String name, String defaultValue, NSDictionary<String, WOAssociation> associations, WOComponent component) {
String value = stringValueForBinding(name, associations, component);
if (value != null) {
return value;
}
return defaultValue;
}
/**
* Retrieves the current string value of the given binding from the component. If there
* is no such binding <code>null</code> will be returned.
*
* @param name binding name
* @param associations array of associations
* @param component component to get value from
* @return retrieved string value or <code>null</code>
*/
public static String stringValueForBinding(String name, NSDictionary<String, WOAssociation> associations, WOComponent component) {
WOAssociation association = bindingNamed(name, associations);
if (association != null) {
return (String) association.valueInComponent(component);
}
return null;
}
/**
* Retrieves the current boolean value of the given binding from the component. If there
* is no such binding the default value will be returned.
*
* @param name binding name
* @param defaultValue default value
* @param associations array of associations
* @param component component to get value from
* @return retrieved boolean value or default value
*/
public static boolean booleanValueForBinding(String name, boolean defaultValue, NSDictionary<String, WOAssociation> associations, WOComponent component) {
WOAssociation association = bindingNamed(name, associations);
if (association != null) {
return association.booleanValueInComponent(component);
}
return defaultValue;
}
/**
* Retrieves the current boolean value of the given binding from the component. If there
* is no such binding <code>false</code> will be returned.
*
* @param name binding name
* @param associations array of associations
* @param component component to get value from
* @return retrieved boolean value or <code>false</code>
*/
public static boolean booleanValueForBinding(String name, NSDictionary<String, WOAssociation> associations, WOComponent component) {
return booleanValueForBinding(name, false, associations, component);
}
/**
* Retrieves the current int value of the given binding from the component. If there
* is no such binding the default value will be returned.
*
* @param name binding name
* @param defaultValue default value
* @param associations array of associations
* @param component component to get value from
* @return retrieved int value or default value
*/
public static int integerValueForBinding(String name, int defaultValue, NSDictionary<String, WOAssociation> associations, WOComponent component) {
WOAssociation association = bindingNamed(name, associations);
if (association != null) {
Object value = association.valueInComponent(component);
return ERXValueUtilities.intValueWithDefault(value, defaultValue);
}
return defaultValue;
}
/**
* Retrieves the current array value of the given binding from the component. If there
* is no such binding or its value evaluates to <code>null</code> the default
* value will be returned.
*
* @param name binding name
* @param defaultValue default value
* @param associations array of associations
* @param component component to get value from
* @return retrieved array value or default value
*/
public static <T> NSArray<T> arrayValueForBinding(String name, NSArray<T> defaultValue, NSDictionary<String, WOAssociation> associations, WOComponent component) {
WOAssociation association = bindingNamed(name, associations);
if (association != null) {
Object value = association.valueInComponent(component);
return ERXValueUtilities.arrayValueWithDefault(value, defaultValue);
}
return defaultValue;
}
/**
* Retrieves the current array value of the given binding from the component. If there
* is no such binding <code>null</code> will be returned.
*
* @param name binding name
* @param associations array of associations
* @param component component to get value from
* @return retrieved array value or <code>null</code>
*/
public static NSArray arrayValueForBinding(String name, NSDictionary<String, WOAssociation> associations, WOComponent component) {
return arrayValueForBinding(name, null, associations, component);
}
}