001    /**
002     * Copyright 2005-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.krad.util.documentserializer;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.kuali.rice.krad.util.KRADConstants;
020    
021    import java.util.StringTokenizer;
022    
023    /**
024     * This is a implementation of a trie/prefix tree of that contains metadata about property serializability
025     * during the document serialization process.
026     *
027     */
028    public class PropertySerializerTrie {
029        private static final String PROPERTY_NAME_COMPONENT_SEPARATOR = ".";
030        private PropertySerializerTrieNode rootNode;
031    
032        public PropertySerializerTrie() {
033            rootNode = new PropertySerializerTrieNode(KRADConstants.EMPTY_STRING, KRADConstants.EMPTY_STRING);
034        }
035    
036        /**
037         * Registers a new serializable property so that all of its primitives are serialized.  All nesting properties
038         * will be serialized only to render open/close tags to maintain consistency with the document structure, unless
039         * they are registered as well.
040         *
041         * For example, if only property "document.a.b" is registered, then the XML will look like the following:
042         *
043         * <document>
044         *     <a>
045         *         <b>
046         *             <primitiveOfB>valueOfPrimitive</primitiveOfB>
047         *         </b>
048         *     </a>
049         * </document>
050         *
051         * That is, primitives of "document" and "document.a" will not be serialized unless those property strings are registered.
052         *
053         * @param propertyName
054         * @param setPropertySerializabilityToObjectAndAllPrimitivesForAll
055         */
056        public void addSerializablePropertyName(String propertyName, boolean setPropertySerializabilityToObjectAndAllPrimitivesForAll) {
057            if (propertyName == null) {
058                throw new IllegalArgumentException("Null attribute name specified");
059            }
060            if (StringUtils.isBlank(propertyName)) {
061                rootNode.setPropertySerializabilityToObjectAndAllPrimitives();
062            }
063            else {
064                StringTokenizer tok = new StringTokenizer(propertyName, PROPERTY_NAME_COMPONENT_SEPARATOR, false);
065                StringBuilder buf = new StringBuilder();
066    
067                if(setPropertySerializabilityToObjectAndAllPrimitivesForAll)
068                    rootNode.setPropertySerializabilityToObjectAndAllPrimitives();
069    
070                PropertySerializerTrieNode currentNode = rootNode;
071                while (tok.hasMoreTokens()) {
072                    String attributeNameComponent = tok.nextToken();
073                    validateAttributeNameComponent(attributeNameComponent);
074    
075                    buf.append(attributeNameComponent);
076    
077                    // create a new node or retrieve existing node for this name component
078                    PropertySerializerTrieNode childNode = currentNode.getChildNode(attributeNameComponent);
079                    if (childNode == null) {
080                        childNode = new PropertySerializerTrieNode(buf.toString(), attributeNameComponent);
081                        currentNode.addChildNode(childNode);
082                    }
083    
084                    if (tok.hasMoreTokens()) {
085                        buf.append(PROPERTY_NAME_COMPONENT_SEPARATOR);
086                    }
087                    currentNode = childNode;
088                    if(setPropertySerializabilityToObjectAndAllPrimitivesForAll)
089                            currentNode.setPropertySerializabilityToObjectAndAllPrimitives();
090                }
091    
092                currentNode.setPropertySerializabilityToObjectAndAllPrimitives();
093            }
094        }
095    
096        /**
097         * Retrieves the metadata about the given property name
098         *
099         * @param propertyName
100         * @return
101         */
102        public PropertySerializabilityMetadata getPropertySerializabilityMetadata(String propertyName) {
103            if (propertyName == null) {
104                throw new IllegalArgumentException("Null attribute name specified");
105            }
106            if (StringUtils.isBlank(propertyName)) {
107                return rootNode;
108            }
109            else {
110                StringTokenizer tok = new StringTokenizer(propertyName, PROPERTY_NAME_COMPONENT_SEPARATOR, false);
111    
112                PropertySerializerTrieNode currentNode = rootNode;
113                while (tok.hasMoreTokens()) {
114                    String attributeNameComponent = tok.nextToken();
115                    validateAttributeNameComponent(attributeNameComponent);
116    
117                    // retrieve the child node for this name component
118                    PropertySerializerTrieNode childNode = currentNode.getChildNode(attributeNameComponent);
119                    if (childNode == null) {
120                        // we didn't find a child node, so we know that something wasn't added with the prefix we're processing
121                        return null;
122                    }
123                    else {
124                        // keep going until we hit the last token, at which case we'll get out of this loop
125                        currentNode = childNode;
126                    }
127                }
128                return currentNode;
129            }
130        }
131    
132        /**
133         * Returns the root node of the trie
134         *
135         * @return
136         */
137        public PropertySerializabilityMetadata getRootPropertySerializibilityMetadata() {
138            return rootNode;
139        }
140    
141        protected void validateAttributeNameComponent(String attributeNameComponent) {
142            if (StringUtils.isBlank(attributeNameComponent)) {
143                throw new IllegalArgumentException("Blank attribute name component specified");
144            }
145        }
146    }