org.xwiki.rendering.xml.internal.serializer.DefaultXMLSerializer.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.rendering.xml.internal.serializer.DefaultXMLSerializer.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.rendering.xml.internal.serializer;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

import javax.inject.Singleton;

import org.apache.commons.lang3.ObjectUtils;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.util.ReflectionUtils;
import org.xwiki.properties.ConverterManager;
import org.xwiki.rendering.listener.descriptor.ListenerDescriptor;
import org.xwiki.rendering.listener.descriptor.ListenerElement;
import org.xwiki.rendering.xml.internal.XMLConfiguration;
import org.xwiki.rendering.xml.internal.XMLUtils;
import org.xwiki.rendering.xml.internal.parameter.ParameterManager;

/**
 * Proxy called as an event lister to produce SAX events.
 * 
 * @version $Id: ebaedecd9a1447a68cd6c199cc35ee1feaec7390 $
 * @since 5.0M1
 */
@Component
@Singleton
public class DefaultXMLSerializer implements InvocationHandler {
    private static final Pattern VALID_ELEMENTNAME = Pattern.compile("[A-Za-z][A-Za-z0-9:_.-]*");

    private ContentHandler contentHandler;

    private ParameterManager parameterManager;

    private ListenerDescriptor descriptor;

    private ConverterManager converter;

    private XMLConfiguration configuration;

    public DefaultXMLSerializer(ContentHandler contentHandler, ParameterManager parameterManager,
            ListenerDescriptor descriptor, ConverterManager converter, XMLConfiguration configuration) {
        this.contentHandler = contentHandler;
        this.parameterManager = parameterManager;
        this.descriptor = descriptor;
        this.converter = converter;
        this.configuration = configuration != null ? configuration : new XMLConfiguration();
    }

    private boolean isValidBlockElementName(String blockName) {
        return VALID_ELEMENTNAME.matcher(blockName).matches()
                && !this.configuration.getElementParameter().equals(blockName);
    }

    private String getBlockName(String eventName, String prefix) {
        String blockName = eventName.substring(prefix.length());
        blockName = Character.toLowerCase(blockName.charAt(0)) + blockName.substring(1);

        return blockName;
    }

    private void addInlineParameters(AttributesImpl attributes, List<Object> parameters, ListenerElement element) {
        for (int i = 0; i < parameters.size(); ++i) {
            Object parameter = parameters.get(i);

            if (parameter != null) {
                Type type = element.getParameters().get(i);
                Class<?> typeClass = ReflectionUtils.getTypeClass(type);

                if (XMLUtils.isSimpleType(typeClass)) {
                    attributes.addAttribute(null, null, this.configuration.getElementParameter() + i, null,
                            this.converter.<String>convert(String.class, parameter));

                    parameters.set(i, null);
                } else if (ObjectUtils.equals(XMLUtils.defaultValue(typeClass), parameter)) {
                    attributes.addAttribute(null, null, this.configuration.getElementParameter() + i, null, "");

                    parameters.set(i, null);
                }
            }
        }
    }

    private AttributesImpl createStartAttributes(String blockName, List<Object> parameters) {
        AttributesImpl attributes = new AttributesImpl();

        if (!isValidBlockElementName(blockName)) {
            attributes.addAttribute(null, null, this.configuration.getAttributeBlockName(), null, blockName);
        }

        if (parameters != null) {
            ListenerElement element = this.descriptor.getElements().get(blockName.toLowerCase());

            addInlineParameters(attributes, parameters, element);
        }

        return attributes;
    }

    private void removeDefaultParameters(List<Object> parameters, ListenerElement descriptor) {
        if (parameters != null) {
            for (int i = 0; i < parameters.size(); ++i) {
                Object value = parameters.get(i);

                if (value != null && !shouldPrintParameter(value, i, descriptor)) {
                    parameters.set(i, null);
                }
            }
        }
    }

    private void beginEvent(String eventName, Object[] parameters) {
        String blockName = getBlockName(eventName, "begin");

        ListenerElement element = this.descriptor.getElements().get(blockName.toLowerCase());

        List<Object> elementParameters = parameters != null ? Arrays.asList(parameters) : null;

        // Remove useless parameters
        removeDefaultParameters(elementParameters, element);

        // Put as attributes parameters which are simple enough to not require full XML serialization
        AttributesImpl attributes = createStartAttributes(blockName, elementParameters);

        // Get proper element name
        String elementName;
        if (isValidBlockElementName(blockName)) {
            elementName = blockName;
        } else {
            elementName = this.configuration.getElementBlock();
        }

        // Print start element
        startElement(elementName, attributes);

        // Print complex parameters
        printParameters(elementParameters, element);
    }

    private void endEvent(String eventName) {
        String blockName = getBlockName(eventName, "end");

        if (isValidBlockElementName(blockName)) {
            endElement(blockName);
        } else {
            endElement(this.configuration.getElementBlock());
        }
    }

    private void onEvent(String eventName, Object[] parameters) {
        String blockName = getBlockName(eventName, "on");

        ListenerElement element = this.descriptor.getElements().get(blockName.toLowerCase());

        List<Object> elementParameters = parameters != null ? Arrays.asList(parameters) : null;

        // Remove useless parameters
        removeDefaultParameters(elementParameters, element);

        // Put as attributes parameters which are simple enough to not require full XML serialization
        AttributesImpl attributes = (elementParameters != null && elementParameters.size() > 1)
                ? createStartAttributes(blockName, Arrays.asList(parameters))
                : new AttributesImpl();

        // Get proper element name
        String elementName;
        if (isValidBlockElementName(blockName)) {
            elementName = blockName;
        } else {
            elementName = this.configuration.getElementBlock();
        }

        // Print start element
        startElement(elementName, attributes);

        // Print complex parameters
        if (parameters != null && parameters.length == 1 && XMLUtils.isSimpleType(element.getParameters().get(0))) {
            String value = parameters[0].toString();
            try {
                this.contentHandler.characters(value.toCharArray(), 0, value.length());
            } catch (SAXException e) {
                throw new RuntimeException("Failed to send sax event", e);
            }
        } else {
            printParameters(elementParameters, element);
        }

        // Print end element
        endElement(elementName);
    }

    private boolean shouldPrintParameter(Object value, int index, ListenerElement descriptor) {
        boolean print = true;

        Type type = descriptor.getParameters().get(index);

        if (type instanceof Class) {
            Class<?> typeClass = (Class<?>) type;
            try {
                if (typeClass.isPrimitive()) {
                    print = !XMLUtils.defaultValue(typeClass).equals(value);
                }
            } catch (Exception e) {
                // Should never happen
            }
        }

        return print;
    }

    private void printParameters(List<Object> parameters, ListenerElement descriptor) {
        if (parameters != null && !parameters.isEmpty()) {
            for (int i = 0; i < parameters.size(); ++i) {
                Object value = parameters.get(i);

                if (value != null && shouldPrintParameter(value, i, descriptor)) {
                    startElement(this.configuration.getElementParameter() + i);

                    this.parameterManager.serialize(descriptor.getParameters().get(i), value, this.contentHandler);

                    endElement(this.configuration.getElementParameter() + i);
                }
            }
        }
    }

    private void startElement(String elemntName) {
        startElement(elemntName, (String[][]) null);
    }

    private void startElement(String elemntName, String[][] parameters) {
        startElement(elemntName, createAttributes(parameters));
    }

    private void startElement(String elemntName, Attributes attributes) {
        try {
            this.contentHandler.startElement("", elemntName, elemntName, attributes);
        } catch (SAXException e) {
            throw new RuntimeException("Failed to send sax event", e);
        }
    }

    private void endElement(String elemntName) {
        try {
            this.contentHandler.endElement("", elemntName, elemntName);
        } catch (SAXException e) {
            throw new RuntimeException("Failed to send sax event", e);
        }
    }

    /**
     * Convert provided table into {@link Attributes} to use in xml writer.
     */
    private Attributes createAttributes(String[][] parameters) {
        AttributesImpl attributes = new AttributesImpl();

        if (parameters != null && parameters.length > 0) {
            for (String[] entry : parameters) {
                attributes.addAttribute(null, null, entry[0], null, entry[1]);
            }
        }

        return attributes;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;

        if (method.getName().equals("setContentHandler")) {
            this.contentHandler = (ContentHandler) args[0];
        } else if (method.getName().equals("getContentHandler")) {
            result = this.contentHandler;
        } else if (method.getName().startsWith("begin")) {
            beginEvent(method.getName(), args);
        } else if (method.getName().startsWith("end")) {
            endEvent(method.getName());
        } else if (method.getName().startsWith("on")) {
            onEvent(method.getName(), args);
        } else {
            throw new NoSuchMethodException(method.toGenericString());
        }

        return result;
    }
}