| package org.apache.velocity.runtime; |
| |
| /* |
| * 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. |
| */ |
| |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import org.apache.velocity.runtime.directive.VelocimacroProxy; |
| import org.apache.velocity.runtime.parser.node.Node; |
| import org.apache.velocity.runtime.parser.node.SimpleNode; |
| import org.apache.velocity.util.MapFactory; |
| |
| /** |
| * Manages VMs in namespaces. Currently, two namespace modes are |
| * supported: |
| * |
| * <ul> |
| * <li>flat - all allowable VMs are in the global namespace</li> |
| * <li>local - inline VMs are added to it's own template namespace</li> |
| * </ul> |
| * |
| * Thanks to <a href="mailto:JFernandez@viquity.com">Jose Alberto Fernandez</a> |
| * for some ideas incorporated here. |
| * |
| * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> |
| * @author <a href="mailto:JFernandez@viquity.com">Jose Alberto Fernandez</a> |
| * @version $Id$ |
| */ |
| public class VelocimacroManager |
| { |
| private static String GLOBAL_NAMESPACE = ""; |
| |
| private boolean registerFromLib = false; |
| |
| /** Hash of namespace hashes. */ |
| private final Map namespaceHash = MapFactory.create(17, 0.5f, 20, false); |
| |
| /** reference to global namespace hash */ |
| private final Map globalNamespace; |
| |
| /** set of names of library tempates/namespaces */ |
| private final Set libraries = Collections.synchronizedSet(new HashSet()); |
| |
| /* |
| * big switch for namespaces. If true, then properties control |
| * usage. If false, no. |
| */ |
| private boolean namespacesOn = true; |
| private boolean inlineLocalMode = false; |
| private boolean inlineReplacesGlobal = false; |
| |
| /** |
| * Adds the global namespace to the hash. |
| */ |
| VelocimacroManager(RuntimeServices rsvc) |
| { |
| /* |
| * add the global namespace to the namespace hash. We always have that. |
| */ |
| |
| globalNamespace = addNamespace(GLOBAL_NAMESPACE); |
| } |
| |
| /** |
| * Adds a VM definition to the cache. |
| * |
| * Called by VelocimacroFactory.addVelociMacro (after parsing and discovery in Macro directive) |
| * |
| * @param vmName Name of the new VelociMacro. |
| * @param macroBody String representation of the macro body. |
| * @param argArray Array of macro parameters, first parameter is the macro name. |
| * @param namespace The namespace/template from which this macro has been loaded. |
| * @return Whether everything went okay. |
| */ |
| public boolean addVM(final String vmName, final Node macroBody, final String argArray[], |
| final String namespace, boolean canReplaceGlobalMacro) |
| { |
| if (macroBody == null) |
| { |
| // happens only if someone uses this class without the Macro directive |
| // and provides a null value as an argument |
| throw new RuntimeException("Null AST for "+vmName+" in "+namespace); |
| } |
| |
| MacroEntry me = new MacroEntry(vmName, macroBody, argArray, namespace); |
| |
| me.setFromLibrary(registerFromLib); |
| |
| /* |
| * the client (VMFactory) will signal to us via |
| * registerFromLib that we are in startup mode registering |
| * new VMs from libraries. Therefore, we want to |
| * addto the library map for subsequent auto reloads |
| */ |
| |
| boolean isLib = true; |
| |
| MacroEntry exist = (MacroEntry) globalNamespace.get(vmName); |
| |
| if (registerFromLib) |
| { |
| libraries.add(namespace); |
| } |
| else |
| { |
| /* |
| * now, we first want to check to see if this namespace (template) |
| * is actually a library - if so, we need to use the global namespace |
| * we don't have to do this when registering, as namespaces should |
| * be shut off. If not, the default value is true, so we still go |
| * global |
| */ |
| |
| isLib = libraries.contains(namespace); |
| } |
| |
| if ( !isLib && usingNamespaces(namespace) ) |
| { |
| /* |
| * first, do we have a namespace hash already for this namespace? |
| * if not, add it to the namespaces, and add the VM |
| */ |
| |
| Map local = getNamespace(namespace, true); |
| local.put(vmName, me); |
| |
| return true; |
| } |
| else |
| { |
| /* |
| * otherwise, add to global template. First, check if we |
| * already have it to preserve some of the autoload information |
| */ |
| |
| |
| if (exist != null) |
| { |
| me.setFromLibrary(exist.getFromLibrary()); |
| } |
| |
| /* |
| * now add it |
| */ |
| |
| globalNamespace.put(vmName, me); |
| |
| return true; |
| } |
| } |
| |
| /** |
| * Gets a VelocimacroProxy object by the name / source template duple. |
| * |
| * @param vmName Name of the VelocityMacro to look up. |
| * @param namespace Namespace in which to look up the macro. |
| * @return A proxy representing the Macro. |
| */ |
| public VelocimacroProxy get(final String vmName, final String namespace) |
| { |
| return(get(vmName, namespace, null)); |
| } |
| |
| /** |
| * Gets a VelocimacroProxy object by the name / source template duple. |
| * |
| * @param vmName Name of the VelocityMacro to look up. |
| * @param namespace Namespace in which to look up the macro. |
| * @param renderingTemplate Name of the template we are currently rendering. |
| * @return A proxy representing the Macro. |
| * @since 1.6 |
| */ |
| public VelocimacroProxy get(final String vmName, final String namespace, final String renderingTemplate) |
| { |
| if( inlineReplacesGlobal && renderingTemplate != null ) |
| { |
| /* |
| * if VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL is true (local macros can |
| * override global macros) and we know which template we are rendering at the |
| * moment, check if local namespace contains a macro we are looking for |
| * if so, return it instead of the global one |
| */ |
| Map local = getNamespace(renderingTemplate, false); |
| if (local != null) |
| { |
| MacroEntry me = (MacroEntry) local.get(vmName); |
| |
| if (me != null) |
| { |
| return me.getProxy(namespace); |
| } |
| } |
| } |
| |
| if (usingNamespaces(namespace)) |
| { |
| Map local = getNamespace(namespace, false); |
| |
| /* |
| * if we have macros defined for this template |
| */ |
| |
| if (local != null) |
| { |
| MacroEntry me = (MacroEntry) local.get(vmName); |
| |
| if (me != null) |
| { |
| return me.getProxy(namespace); |
| } |
| } |
| } |
| |
| /* |
| * if we didn't return from there, we need to simply see |
| * if it's in the global namespace |
| */ |
| |
| MacroEntry me = (MacroEntry) globalNamespace.get(vmName); |
| |
| if (me != null) |
| { |
| return me.getProxy(namespace); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Removes the VMs and the namespace from the manager. |
| * Used when a template is reloaded to avoid |
| * losing memory. |
| * |
| * @param namespace namespace to dump |
| * @return boolean representing success |
| */ |
| public boolean dumpNamespace(final String namespace) |
| { |
| synchronized(this) |
| { |
| if (usingNamespaces(namespace)) |
| { |
| Map h = (Map) namespaceHash.remove(namespace); |
| |
| if (h == null) |
| { |
| return false; |
| } |
| |
| h.clear(); |
| |
| return true; |
| } |
| |
| return false; |
| } |
| } |
| |
| /** |
| * public switch to let external user of manager to control namespace |
| * usage indep of properties. That way, for example, at startup the |
| * library files are loaded into global namespace |
| * |
| * @param namespaceOn True if namespaces should be used. |
| */ |
| public void setNamespaceUsage(final boolean namespaceOn) |
| { |
| this.namespacesOn = namespaceOn; |
| } |
| |
| /** |
| * Should macros registered from Libraries be marked special? |
| * @param registerFromLib True if macros from Libs should be marked. |
| */ |
| public void setRegisterFromLib(final boolean registerFromLib) |
| { |
| this.registerFromLib = registerFromLib; |
| } |
| |
| /** |
| * Should macros from the same template be inlined? |
| * |
| * @param inlineLocalMode True if macros should be inlined on the same template. |
| */ |
| public void setTemplateLocalInlineVM(final boolean inlineLocalMode) |
| { |
| this.inlineLocalMode = inlineLocalMode; |
| } |
| |
| /** |
| * returns the hash for the specified namespace, and if it doesn't exist |
| * will create a new one and add it to the namespaces |
| * |
| * @param namespace name of the namespace :) |
| * @param addIfNew flag to add a new namespace if it doesn't exist |
| * @return namespace Map of VMs or null if doesn't exist |
| */ |
| private Map getNamespace(final String namespace, final boolean addIfNew) |
| { |
| Map h = (Map) namespaceHash.get(namespace); |
| |
| if (h == null && addIfNew) |
| { |
| h = addNamespace(namespace); |
| } |
| |
| return h; |
| } |
| |
| /** |
| * adds a namespace to the namespaces |
| * |
| * @param namespace name of namespace to add |
| * @return Hash added to namespaces, ready for use |
| */ |
| private Map addNamespace(final String namespace) |
| { |
| Map h = MapFactory.create(17, 0.5f, 20, false); |
| Object oh; |
| |
| if ((oh = namespaceHash.put(namespace, h)) != null) |
| { |
| /* |
| * There was already an entry on the table, restore it! |
| * This condition should never occur, given the code |
| * and the fact that this method is private. |
| * But just in case, this way of testing for it is much |
| * more efficient than testing before hand using get(). |
| */ |
| namespaceHash.put(namespace, oh); |
| /* |
| * Should't we be returning the old entry (oh)? |
| * The previous code was just returning null in this case. |
| */ |
| return null; |
| } |
| |
| return h; |
| } |
| |
| /** |
| * determines if currently using namespaces. |
| * |
| * @param namespace currently ignored |
| * @return true if using namespaces, false if not |
| */ |
| private boolean usingNamespaces(final String namespace) |
| { |
| /* |
| * if the big switch turns of namespaces, then ignore the rules |
| */ |
| |
| if (!namespacesOn) |
| { |
| return false; |
| } |
| |
| /* |
| * currently, we only support the local template namespace idea |
| */ |
| |
| if (inlineLocalMode) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Return the library name for a given macro. |
| * @param vmName Name of the Macro to look up. |
| * @param namespace Namespace to look the macro up. |
| * @return The name of the library which registered this macro in a namespace. |
| */ |
| public String getLibraryName(final String vmName, final String namespace) |
| { |
| if (usingNamespaces(namespace)) |
| { |
| Map local = getNamespace(namespace, false); |
| |
| /* |
| * if we have this macro defined in this namespace, then |
| * it is masking the global, library-based one, so |
| * just return null |
| */ |
| |
| if ( local != null) |
| { |
| MacroEntry me = (MacroEntry) local.get(vmName); |
| |
| if (me != null) |
| { |
| return null; |
| } |
| } |
| } |
| |
| /* |
| * if we didn't return from there, we need to simply see |
| * if it's in the global namespace |
| */ |
| |
| MacroEntry me = (MacroEntry) globalNamespace.get(vmName); |
| |
| if (me != null) |
| { |
| return me.getSourceTemplate(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * @since 1.6 |
| */ |
| public void setInlineReplacesGlobal(boolean is) |
| { |
| inlineReplacesGlobal = is; |
| } |
| |
| |
| /** |
| * wrapper class for holding VM information |
| */ |
| private static class MacroEntry |
| { |
| private final String vmName; |
| private final String[] argArray; |
| private final String sourceTemplate; |
| private SimpleNode nodeTree = null; |
| private boolean fromLibrary = false; |
| private VelocimacroProxy vp; |
| |
| private MacroEntry(final String vmName, final Node macro, |
| final String argArray[], final String sourceTemplate) |
| { |
| this.vmName = vmName; |
| this.argArray = argArray; |
| this.nodeTree = (SimpleNode)macro; |
| this.sourceTemplate = sourceTemplate; |
| |
| vp = new VelocimacroProxy(); |
| vp.setName(this.vmName); |
| vp.setArgArray(this.argArray); |
| vp.setNodeTree(this.nodeTree); |
| } |
| |
| /** |
| * Has the macro been registered from a library. |
| * @param fromLibrary True if the macro was registered from a Library. |
| */ |
| public void setFromLibrary(final boolean fromLibrary) |
| { |
| this.fromLibrary = fromLibrary; |
| } |
| |
| /** |
| * Returns true if the macro was registered from a library. |
| * @return True if the macro was registered from a library. |
| */ |
| public boolean getFromLibrary() |
| { |
| return fromLibrary; |
| } |
| |
| /** |
| * Returns the node tree for this macro. |
| * @return The node tree for this macro. |
| */ |
| public SimpleNode getNodeTree() |
| { |
| return nodeTree; |
| } |
| |
| /** |
| * Returns the source template name for this macro. |
| * @return The source template name for this macro. |
| */ |
| public String getSourceTemplate() |
| { |
| return sourceTemplate; |
| } |
| |
| VelocimacroProxy getProxy(final String namespace) |
| { |
| /* |
| * FIXME: namespace data is omitted, this probably |
| * breaks some error reporting? |
| */ |
| return vp; |
| } |
| } |
| } |
| |
| |