/*
* 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.myfaces.trinidadinternal.share.xml;
import java.io.InputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidadinternal.share.config.Configuration;
import org.apache.myfaces.trinidadinternal.share.config.ConfigurationImpl;
import org.apache.myfaces.trinidadinternal.share.io.CachingInputStreamProvider;
import org.apache.myfaces.trinidadinternal.share.io.DefaultNameResolver;
import org.apache.myfaces.trinidadinternal.share.io.InputStreamProvider;
import org.apache.myfaces.trinidadinternal.share.io.NameResolver;
import org.apache.myfaces.trinidadinternal.share.expl.JavaMethod;
/**
* Utility class for XML parsing.
* <p>
* @version $Name: $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/share/xml/XMLUtils.java#0 $) $Date: 10-nov-2005.18:59:17 $
*/
public class XMLUtils
{
/**
* Parses an XML file, like the skinning .xss file. For includes, see parseInclude().
* @param context the current ParseContext, which will be cloned
* @param xmlProvider an XML provider
* @param manager a ParserManager
* @param resolver a NameResolver to locate the target
* @param sourceName the name of the target, relative to the current file
* @param expectedType the expected Java type of the target.
*/
static public Object parseSource(
ParseContext context,
XMLProvider xmlProvider,
ParserManager manager,
NameResolver resolver,
String sourceName,
Class<?> expectedType) throws IOException, SAXException
{
if (manager == null)
throw new NullPointerException();
if (expectedType == null)
throw new NullPointerException();
if (resolver == null)
throw new NullPointerException();
if (sourceName == null)
throw new NullPointerException();
if (context == null)
throw new NullPointerException();
if (xmlProvider == null)
xmlProvider = new JaxpXMLProvider();
InputStreamProvider provider = resolver.getProvider(sourceName);
Object cached = provider.getCachedResult();
if ((cached != null) && expectedType.isInstance(cached))
return cached;
TreeBuilder builder = new TreeBuilder(manager, expectedType);
InputStream stream = provider.openInputStream();
try
{
InputSource source = new InputSource(stream);
source.setSystemId(sourceName);
// Store a resolver relative to the file we're about to parse
setResolver(context, resolver.getResolver(sourceName));
setInputStreamProvider(context, provider);
// Step 6. Parse!
Object value = builder.parse(xmlProvider, source, context);
// Step 7. Store the cached result (if successful)
if (value != null)
provider.setCachedResult(value);
return value;
}
finally
{
stream.close();
}
}
/**
* Parses an include of an XML file. The include will be located using
* an already-stored NameResolver object.
*
* @see #getResolver
* @see #setResolver
* @param context the current ParseContext, which will be cloned
* @param sourceName the name of the target, relative to the current file
* @param expectedType the expected Java type of the target.
*/
@SuppressWarnings("unchecked")
static public Object parseInclude(
ParseContext context,
String sourceName,
Class<?> expectedType) throws IOException, SAXException
{
// Step 1. Find the name resolver.
NameResolver resolver = getResolver(context);
if (resolver == null)
{
_log(context, "Internal error: couldn't find NameResolver");
return null;
}
// Step 2. Find an InputStreamProvider. Mark a dependency on the base
// provider (if necessary)
InputStreamProvider provider = resolver.getProvider(sourceName);
InputStreamProvider baseProvider = getInputStreamProvider(context);
if (baseProvider instanceof CachingInputStreamProvider)
{
((CachingInputStreamProvider) baseProvider).addCacheDependency(provider);
}
// Step 3. Detect if this will be a circular include.
ArrayList<Object> list =
(ArrayList<Object>) context.getProperty(_SHARE_NAMESPACE, "_includeStack");
Object identifier = provider.getIdentifier();
if ((list != null) && (list.contains(identifier)))
{
// =-=AEW Just logging an error isn't really enough - the include
// will fail, but parsing continues. So, instead, we throw
// an exception...
throw new SAXParseException(_LOG.getMessage(
"CIRCULAR_INCLUDE_DETECTED", sourceName), context.getLocator());
}
// Step 4. Try to get a cached version
Object cached = provider.getCachedResult();
if ((cached != null) && expectedType.isInstance(cached))
return cached;
// Step 5. Set up the new context; first, clone the original
ParseContext newContext = (ParseContext) context.clone();
// Add the current identifer to the stack (used for detecting
// circular includes) placed on the ParseContext
if (list == null)
list = new ArrayList<Object>();
else
list = (ArrayList<Object>) list.clone();
list.add(identifier);
newContext.setProperty(_SHARE_NAMESPACE, "_includeStack", list);
ParserManager parserManager =context.getParserManager();
TreeBuilder builder = new TreeBuilder(parserManager,
expectedType);
InputStream stream = provider.openInputStream();
try
{
InputSource source = new InputSource(stream);
source.setSystemId(sourceName);
setResolver(newContext, resolver.getResolver(sourceName));
setInputStreamProvider(newContext, provider);
// Step 6. Parse!
Object value = builder.parse(context.getXMLProvider(),
source,
newContext);
// Step 7. Store the cached result (if successful)
if (value != null)
provider.setCachedResult(value);
return value;
}
finally
{
stream.close();
}
}
/**
* Gets the NameResolver stored on the ParseContext.
*/
static public NameResolver getResolver(ParseContext context)
{
NameResolver source = (NameResolver)
context.getProperty(_SHARE_NAMESPACE, "_nameResolver");
if (source == null)
source = new DefaultNameResolver(null, null);
return source;
}
/**
* Stores a NameResolver on a ParseContext.
*/
static public void setResolver(ParseContext context, NameResolver source)
{
context.setProperty(_SHARE_NAMESPACE, "_nameResolver", source);
}
/**
* Gets the InputStreamProvider stored on the ParseContext.
*/
static public InputStreamProvider getInputStreamProvider(
ParseContext context)
{
return (InputStreamProvider)
context.getProperty(_SHARE_NAMESPACE, "_provider");
}
/**
* Stores a InputStreamProvider on a ParseContext.
*/
static public void setInputStreamProvider(
ParseContext context, InputStreamProvider provider)
{
context.setProperty(_SHARE_NAMESPACE, "_provider", provider);
}
/**
* Convenience function for setting an XML provider on a
* configuration.
*/
static public void setXMLProvider(
ConfigurationImpl config,
String providerClassName)
{
config.putProperty(Configuration.XML_PROVIDER,
providerClassName);
}
/**
* Sets a "local" ParseContext property. ParseContext properties
* set with the standard setProperty() method will still be
* available in included files, and if set inside an included
* file will be available to the parent file. Local properties
* are available to this document only.
* @see #getLocalProperty
*/
static public void setLocalProperty(
ParseContext context,
String namespace,
Object key,
Object value)
{
if (value != null)
value = new Object[]{context, value};
context.setProperty(namespace, key, value);
}
/**
* Gets a "local" ParseContext property. ParseContext properties
* set with the standard setProperty() method will still be
* available in included files, and if set inside an included
* file will be available to the parent file. Local properties
* are available to this document only.
* @see #setLocalProperty
*/
static public Object getLocalProperty(
ParseContext context,
String namespace,
Object key)
{
Object o = context.getProperty(namespace, key);
if (o instanceof Object[])
{
Object[] array = (Object[]) o;
if ((array.length == 2) && (array[0] instanceof ParseContext))
{
ParseContext contextWhenSet = (ParseContext) array[0];
if (_unwrapContext(contextWhenSet) == _unwrapContext(context))
{
return array[1];
}
}
}
return null;
}
/**
* Convenience object for getting an XML provider off
* a Configuration object.
* @param config the configuration object
* @param log an (optional) error log
* @return an XMLProvider implementation
*/
static public XMLProvider getXMLProvider(
Configuration config)
{
return new JaxpXMLProvider();
}
/**
* Parses a whitespace separated series of name tokens.
* @param stringValue the full string
* @return an array of each constituent value, or null
* if there are no tokens (that is, the string is empty or
* all whitespace)
*/
static public String[] parseNameTokens(String stringValue)
{
List<String> list = parseNameTokensAsList(stringValue);
if (list == null)
return null;
else
return list.toArray(new String[list.size()]);
}
/**
* Parses a whitespace separated series of name tokens.
* @param stringValue the full string
* @return a set of each constituent value, or null
* if there are no tokens (that is, the string is empty or
* all whitespace)
*/
static public Set<String> parseNameTokensAsSet(String stringValue)
{
List<String> list = parseNameTokensAsList(stringValue);
if (list == null)
return null;
else
return new HashSet(list);
}
/**
* Parses a whitespace separated series of name tokens.
* @param stringValue the full string
* @return a list of each constituent value, or null
* if there are no tokens (that is, the string is empty or
* all whitespace)
*/
static public List<String> parseNameTokensAsList(String stringValue)
{
if (stringValue == null)
return null;
ArrayList<String> list = new ArrayList<String>(5);
int length = stringValue.length();
boolean inSpace = true;
int start = 0;
for (int i = 0; i < length; i++)
{
char ch = stringValue.charAt(i);
// We're in whitespace; if we've just departed
// a run of non-whitespace, append a string.
// Now, why do we use the supposedly deprecated "Character.isSpace()"
// function instead of "isWhitespace"? We're following XML rules
// here for the meaning of whitespace, which specifically
// EXCLUDES general Unicode spaces.
if (Character.isWhitespace(ch))
{
if (!inSpace)
{
list.add(stringValue.substring(start, i));
inSpace = true;
}
}
// We're out of whitespace; if we've just departed
// a run of whitespace, start keeping track of this string
else
{
if (inSpace)
{
start = i;
inSpace = false;
}
}
}
if (!inSpace)
list.add(stringValue.substring(start));
if (list.isEmpty())
return null;
return list;
}
/**
* add the methods of a class to the list of available EL functions.
* @param namespace the namespace to register the methods under
* @param klass only the public static methods declared on this class are
* inspected
*/
public static void registerFunctions(
ParserManager manager,
String namespace,
Class<?> klass)
{
Method[] methods = klass.getDeclaredMethods();
for(int i=0; i<methods.length; i++)
{
Method met = methods[i];
int mod = met.getModifiers();
if (Modifier.isStatic(mod) && Modifier.isPublic(mod))
{
manager.registerFunction(namespace, met.getName(),
new JavaMethod(met));
}
}
}
static private ParseContext _unwrapContext(ParseContext context)
{
if (context instanceof ParseContextWrapper)
context = ((ParseContextWrapper) context).__getWrappedContext();
return context;
}
private static void _log(ParseContext context, String message)
{
if (_LOG.isWarning())
_LOG.warning(ParseErrorUtils.getErrorMessage(context, message));
}
private XMLUtils()
{
}
// Perhaps move to ShareConstants
static private final String _SHARE_NAMESPACE =
"http://myfaces.apache.org/uix/share";
private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(XMLUtils.class);
}