All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorUpdater Maven / Gradle / Ivy

There is a newer version: 4.15.102
Show newest version
/*
 *  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.ivy.plugins.parser.xml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.StringTokenizer;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.ivy.core.module.descriptor.Configuration;
import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.descriptor.ExtendsDescriptor;
import org.apache.ivy.core.module.descriptor.InheritableItem;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleId;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.plugins.namespace.NameSpaceHelper;
import org.apache.ivy.plugins.namespace.Namespace;
import org.apache.ivy.plugins.parser.ParserSettings;
import org.apache.ivy.plugins.repository.Resource;
import org.apache.ivy.plugins.repository.file.FileResource;
import org.apache.ivy.plugins.repository.url.URLResource;
import org.apache.ivy.util.Checks;
import org.apache.ivy.util.DateUtil;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.XMLHelper;
import org.apache.ivy.util.extendable.ExtendableItemHelper;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Used to update ivy files. Uses ivy file as source and not ModuleDescriptor to preserve as much as
 * possible the original syntax
 */
public final class XmlModuleDescriptorUpdater {
    //CheckStyle:StaticVariableName| OFF    
    //LINE_SEPARATOR is actually a constant, but we have to modify it for the tests
    public static String LINE_SEPARATOR = System.getProperty("line.separator");
    //CheckStyle:StaticVariableName| ON
    
    private XmlModuleDescriptorUpdater() {
    }
    
    /**
     * used to copy a module descriptor xml file (also known as ivy file) and update the revisions
     * of its dependencies, its status and revision
     * 
     * @param srcURL
     *            the url of the source module descriptor file
     * @param destFile
     *            The file to which the updated module descriptor should be output
     */
    public static void update(URL srcURL, File destFile, UpdateOptions options) 
            throws IOException, SAXException {
        if (destFile.getParentFile() != null) {
            destFile.getParentFile().mkdirs();
        }
        OutputStream destStream = new FileOutputStream(destFile);
        try {
            update(srcURL, destStream, options);
        } finally {
            try {
                destStream.close();
            } catch (IOException e) {
                Message.warn("failed to close a stream : " + e.toString());
            }
        }
    }

    public static void update(URL srcURL, OutputStream destFile, UpdateOptions options) 
            throws IOException, SAXException {
        InputStream in = srcURL.openStream();
        try {
            update(srcURL, in, destFile, options);
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                Message.warn("failed to close a stream : " + e.toString());
            }
            try {
                destFile.close();
            } catch (IOException e) {
                Message.warn("failed to close a stream : " + e.toString());
            }
        }

    }

    
    public static void update(InputStream in, Resource res, 
            File destFile, UpdateOptions options) throws IOException, SAXException {
        if (destFile.getParentFile() != null) {
            destFile.getParentFile().mkdirs();
        }
        OutputStream fos = new FileOutputStream(destFile);
        try {
            //TODO: use resource as input stream context?
            URL inputStreamContext = null;
            if (res instanceof URLResource) {
                inputStreamContext = ((URLResource) res).getURL();
            } else if (res instanceof FileResource) {
                inputStreamContext = ((FileResource) res).getFile().toURI().toURL();
            }
            update(inputStreamContext, in, fos, options);
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                Message.warn("failed to close a stream : " + e.toString());
            }
            try {
                fos.close();
            } catch (IOException e) {
                Message.warn("failed to close a stream : " + e.toString());
            }
        }
    }

    private static class UpdaterHandler extends DefaultHandler implements LexicalHandler {

        /** standard attributes of ivy-module/info */
        private static final Collection STD_ATTS = Arrays.asList(new String[] {"organisation",
                "module", "branch", "revision", "status", "publication", "namespace"});

        /** elements that may appear inside ivy-module, in expected order */
        private static final List MODULE_ELEMENTS = Arrays.asList(new String[] {
                "info", "configurations", "publications", "dependencies", "conflicts"
        });
        /** element position of "configurations" inside "ivy-module" */
        private static final int CONFIGURATIONS_POSITION = MODULE_ELEMENTS.indexOf("configurations");
        /** element position of "dependencies" inside "ivy-module" */
        private static final int DEPENDENCIES_POSITION = MODULE_ELEMENTS.indexOf("dependencies");

        /** elements that may appear inside of ivy-module/info */
        private static final Collection INFO_ELEMENTS = Arrays.asList(new String[] {"extends",
                "ivyauthor", "license", "repository", "description"});

        private final ParserSettings settings;

        private final PrintWriter out;

        private final Map resolvedRevisions;
        
        private final Map resolvedBranches;

        private final String status;

        private final String revision;

        private final Date pubdate;

        private final Namespace ns;

        private final boolean replaceInclude;
        
        private final boolean generateRevConstraint;

        private boolean inHeader = true;

        private final List confs;

        private final URL relativePathCtx;
        
        private final UpdateOptions options;

        public UpdaterHandler(URL relativePathCtx, PrintWriter out, final UpdateOptions options) {
            this.options = options;
            this.settings = options.getSettings();
            this.out = out;
            this.resolvedRevisions = options.getResolvedRevisions();
            this.resolvedBranches = options.getResolvedBranches();
            this.status = options.getStatus();
            this.revision = options.getRevision();
            this.pubdate = options.getPubdate();
            this.ns = options.getNamespace();
            this.replaceInclude = options.isReplaceInclude();
            this.generateRevConstraint = options.isGenerateRevConstraint();
            this.relativePathCtx = relativePathCtx;
            if (options.getConfsToExclude() != null) {
                this.confs = Arrays.asList(options.getConfsToExclude());
            } else {
                this.confs = Collections.EMPTY_LIST;
            }
        }

        // never print *ln* cause \n is found in copied characters stream
        // nor do we need do handle indentation, original one is maintained except for attributes

        private String organisation = null;

        // defaultConfMapping of imported configurations, if any
        private String defaultConfMapping = null; 

        // confMappingOverride of imported configurations, if any
        private Boolean confMappingOverride = null; 

        // used to know if the last open tag was empty, to adjust termination 
        // with /> instead of >
        private String justOpen = null;
        
        //track the size of the left indent, so that inserted elements are formatted
        //like nearby elements.

        //true when we're reading indent whitespace
        private boolean indenting;
        private StringBuffer currentIndent = new StringBuffer();
        private ArrayList indentLevels = new ArrayList(); // ArrayList

        //true if an ivy-module/info/description element has been found in the published descriptor
        private boolean hasDescription = false;
        //true if merged configurations have been written
        private boolean mergedConfigurations = false;
        //true if merged deps have been written
        private boolean mergedDependencies = false;

        // the new value of the defaultconf attribute on the publications tag
        private String newDefaultConf = null;
        
        private Stack context = new Stack();

        private Stack buffers = new Stack();

        private Stack confAttributeBuffers = new Stack();

        public void startElement(String uri, String localName, String qName, Attributes attributes)
                throws SAXException {
            inHeader = false;
            endIndent();
            if (justOpen != null) {
                write(">");
            }

            flushMergedElementsBefore(qName);
            
            // according to ivy.xsd, all  elements must occur before
            // the ,  or  elements
            if (options.isMerge() 
                    && ("exclude".equals(localName)
                            || "override".equals(localName)
                            || "conflict".equals(localName))
                    && "ivy-module/dependencies".equals(getContext())) {
                ModuleDescriptor merged = options.getMergedDescriptor();
                writeInheritedDependencies(merged);
                out.println();
                out.print(getIndent());
            }

            context.push(qName);

            String path = getContext();
            if ("info".equals(qName)) {
                infoStarted(attributes);
            } else if (replaceInclude && "include".equals(qName)
                    && context.contains("configurations")) {
                //TODO, in the case of !replaceInclude, we should still replace the relative path
                //by an absolute path. 
                includeStarted(attributes);
            } else if ("ivy-module/info/extends".equals(path)) {
                startExtends(attributes);
            } else if ("ivy-module/dependencies/dependency".equals(path)) {
                startElementInDependency(attributes);
            } else if ("dependencies".equals(qName)) {
                startDependencies(attributes);
            } else if ("ivy-module/configurations/conf".equals(path)) {
                startElementInConfigurationsConf(qName, attributes);
            } else if ("ivy-module/publications/artifact/conf".equals(path)
                    || "ivy-module/dependencies/dependency/conf".equals(path)
                    || "ivy-module/dependencies/dependency/artifact/conf".equals(path)) {
                buffers.push(new ExtendedBuffer(getContext()));
                ((ExtendedBuffer) confAttributeBuffers.peek()).setDefaultPrint(false);
                String confName = substitute(settings, attributes.getValue("name"));
                if (!confs.contains(confName)) {
                    ((ExtendedBuffer) confAttributeBuffers.peek()).setPrint(true);
                    ((ExtendedBuffer) buffers.peek()).setPrint(true);
                    write("<" + qName);
                    for (int i = 0; i < attributes.getLength(); i++) {
                        write(" " + attributes.getQName(i) + "=\""
                                + substitute(settings, attributes.getValue(i)) + "\"");
                    }
                }
            } else if ("ivy-module/publications/artifact".equals(path)) {
                ExtendedBuffer buffer = new ExtendedBuffer(getContext());
                buffers.push(buffer);
                confAttributeBuffers.push(buffer);
                write("<" + qName);
                buffer.setDefaultPrint(attributes.getValue("conf") == null
                        && ((newDefaultConf == null) || (newDefaultConf.length() > 0)));
                for (int i = 0; i < attributes.getLength(); i++) {
                    String attName = attributes.getQName(i);
                    if ("conf".equals(attName)) {
                        String confName = substitute(settings, attributes.getValue("conf"));
                        String newConf = removeConfigurationsFromList(confName, confs);
                        if (newConf.length() > 0) {
                            write(" " + attributes.getQName(i) + "=\"" + newConf + "\"");
                            ((ExtendedBuffer) buffers.peek()).setPrint(true);
                        }
                    } else {
                        write(" " + attributes.getQName(i) + "=\""
                                + substitute(settings, attributes.getValue(i)) + "\"");
                    }
                }
            } else if ("ivy-module/dependencies/dependency/artifact".equals(path)) {
                ExtendedBuffer buffer = new ExtendedBuffer(getContext());
                buffers.push(buffer);
                confAttributeBuffers.push(buffer);
                write("<" + qName);
                buffer.setDefaultPrint(attributes.getValue("conf") == null);
                for (int i = 0; i < attributes.getLength(); i++) {
                    String attName = attributes.getQName(i);
                    if ("conf".equals(attName)) {
                        String confName = substitute(settings, attributes.getValue("conf"));
                        String newConf = removeConfigurationsFromList(confName, confs);
                        if (newConf.length() > 0) {
                            write(" " + attributes.getQName(i) + "=\"" + newConf + "\"");
                            ((ExtendedBuffer) buffers.peek()).setPrint(true);
                        }
                    } else {
                        write(" " + attributes.getQName(i) + "=\""
                                + substitute(settings, attributes.getValue(i)) + "\"");
                    }
                }
            } else if ("ivy-module/publications".equals(path)) {
                startPublications(attributes);
            } else {
                if (options.isMerge() && path.startsWith("ivy-module/info")) {
                    ModuleDescriptor merged = options.getMergedDescriptor();
                    if (path.equals("ivy-module/info/description")) {
                        //if the descriptor already contains a description, don't bother printing
                        //the merged version.
                        hasDescription = true;
                    } else if (!INFO_ELEMENTS.contains(qName)) {
                        //according to the XSD, we should write description after all of the other
                        //standard  elements but before any extended elements.
                        writeInheritedDescription(merged);
                    }
                }
                
                // copy
                write("<" + qName);
                for (int i = 0; i < attributes.getLength(); i++) {
                    write(" " + attributes.getQName(i) + "=\""
                            + substitute(settings, attributes.getValue(i)) + "\"");
                }
            }
            justOpen = qName;
            // indent.append("\t");
        }

        private void startExtends(Attributes attributes) {
            // in merge mode, comment out extends element
            if (options.isMerge()) {
                write("");
        }

        /**
         * Collect the given list of inherited descriptor items into lists keyed by parent Id.
         * Thus all of the items inherited from parent A can be written together, then all of
         * the items from parent B, and so on.
         * @param merged the merged child descriptor
         * @param items the inherited items to collate
         * @return maps parent ModuleRevisionId to a List of InheritedItems imported from that parent
         */
        private Map/**/ collateInheritedItems(ModuleDescriptor merged, 
                InheritableItem[] items) {
            LinkedHashMap/**/ inheritedItems = new LinkedHashMap();
            for (int i = 0; i < items.length; ++i) {
                ModuleRevisionId source = items[i].getSourceModule();
                //ignore items that are defined directly in the child descriptor
                if (source != null 
                    && !source.getModuleId().equals(merged.getModuleRevisionId().getModuleId())) {
                    List accum = (List) inheritedItems.get(source);
                    if (accum == null) {
                        accum = new ArrayList();
                        inheritedItems.put(source, accum);
                    }
                    accum.add(items[i]);
                }
            }
            return inheritedItems;
        }

        /**
         * If no info/description element has yet been written, write the description inherited from
         * the parent descriptor, if any.  Calling this method more than once has no affect.
         */
        private void writeInheritedDescription(ModuleDescriptor merged) {
            if (!hasDescription) {
                hasDescription = true;
                String description = merged.getDescription();
                if (description != null) {
                    PrintWriter writer = getWriter();
                    if (justOpen != null) {
                        writer.println(">");
                    }
                    writeInheritanceComment("description", "parent");
                    writer.println(getIndent() + "" + XMLHelper.escape(description) + "");
                    //restore the indent that existed before we wrote the extra elements
                    writer.print(currentIndent);
                    justOpen = null;
                }
            }
        }

        private void writeInheritedConfigurations(ModuleDescriptor merged) {
            if (!mergedConfigurations) {
                mergedConfigurations = true;
                writeInheritedItems(merged, merged.getConfigurations(),
                    ConfigurationPrinter.INSTANCE, "configurations", false);
            }
        }

        private void writeInheritedDependencies(ModuleDescriptor merged) {
            if (!mergedDependencies) {
                mergedDependencies = true;
                writeInheritedItems(merged, merged.getDependencies(),
                    DependencyPrinter.INSTANCE, "dependencies", false);
            }
        }

        /**
         * 

If publishing in merge mode, guarantee that any merged elements appearing * before moduleElement have been written. This method should * be called before we write the start tag of moduleElement. * This covers cases where merged elements like "configurations" and "dependencies" appear * in the parent descriptor, but are completely missing in the child descriptor.

* *

For example, if "moduleElement" is "dependencies", guarantees that "configurations" * has been written. If moduleElement is null, then all * missing merged elements will be flushed.

* * @param moduleElement a descriptor element name, for example "configurations" or "info" */ private void flushMergedElementsBefore(String moduleElement) { if (options.isMerge() && context.size() == 1 && "ivy-module".equals(context.peek()) && !(mergedConfigurations && mergedDependencies)) { //calculate the position of the element in ivy-module int position = moduleElement == null ? MODULE_ELEMENTS.size() : MODULE_ELEMENTS.indexOf(moduleElement); ModuleDescriptor merged = options.getMergedDescriptor(); //see if we should write if (!mergedConfigurations && position > CONFIGURATIONS_POSITION && merged.getConfigurations().length > 0) { mergedConfigurations = true; writeInheritedItems(merged, merged.getConfigurations(), ConfigurationPrinter.INSTANCE, "configurations", true); } //see if we should write if (!mergedDependencies && position > DEPENDENCIES_POSITION && merged.getDependencies().length > 0) { mergedDependencies = true; writeInheritedItems(merged, merged.getDependencies(), DependencyPrinter.INSTANCE, "dependencies", true); } } } private void flushAllMergedElements() { flushMergedElementsBefore(null); } public void endElement(String uri, String localName, String qName) throws SAXException { String path = getContext(); if (options.isMerge()) { ModuleDescriptor merged = options.getMergedDescriptor(); if ("ivy-module/info".equals(path)) { //guarantee that inherited description has been written before //info element closes. writeInheritedDescription(merged); } else if ("ivy-module/configurations".equals(path)) { //write inherited configurations after all child configurations writeInheritedConfigurations(merged); } else if ("ivy-module/dependencies".equals(path)) { //write inherited dependencies after all child dependencies writeInheritedDependencies(merged); } else if ("ivy-module".equals(path)) { //write any remaining inherited data before we close the //descriptor. flushAllMergedElements(); } } if (qName.equals(justOpen)) { write("/>"); } else { write(""); } if (!buffers.isEmpty()) { ExtendedBuffer buffer = (ExtendedBuffer) buffers.peek(); if (buffer.getContext().equals(path)) { buffers.pop(); if (buffer.isPrint()) { write(buffer.toString()); } } } if (!confAttributeBuffers.isEmpty()) { ExtendedBuffer buffer = (ExtendedBuffer) confAttributeBuffers.peek(); if (buffer.getContext().equals(path)) { confAttributeBuffers.pop(); } } // element is commented out when in merge mode. if (options.isMerge() && "ivy-module/info/extends".equals(path)) { write(" -->"); } justOpen = null; context.pop(); } public void endDocument() throws SAXException { out.print(LINE_SEPARATOR); out.flush(); out.close(); } public void processingInstruction(String target, String data) throws SAXException { write(""); write(LINE_SEPARATOR); } public void warning(SAXParseException e) throws SAXException { throw e; } public void error(SAXParseException e) throws SAXException { throw e; } public void fatalError(SAXParseException e) throws SAXException { throw e; } public void endCDATA() throws SAXException { } public void endDTD() throws SAXException { } public void startCDATA() throws SAXException { } public void comment(char[] ch, int start, int length) throws SAXException { if (justOpen != null) { write(">"); justOpen = null; } StringBuffer comment = new StringBuffer(); comment.append(ch, start, length); write(""); if (inHeader) { write(LINE_SEPARATOR); } } public void endEntity(String name) throws SAXException { } public void startEntity(String name) throws SAXException { } public void startDTD(String name, String publicId, String systemId) throws SAXException { } } public static void update(URL inStreamCtx, InputStream inStream, OutputStream outStream, final UpdateOptions options) throws IOException, SAXException { final PrintWriter out = new PrintWriter(new OutputStreamWriter(outStream, "UTF-8")); out.write(""); out.write(LINE_SEPARATOR); try { UpdaterHandler updaterHandler = new UpdaterHandler(inStreamCtx, out, options); InputSource inSrc = new InputSource(new BufferedInputStream(inStream)); if (inStreamCtx != null) { inSrc.setSystemId(inStreamCtx.toExternalForm()); } XMLHelper.parse(inSrc, null, updaterHandler, updaterHandler); } catch (ParserConfigurationException e) { IllegalStateException ise = new IllegalStateException( "impossible to update Ivy files: parser problem"); ise.initCause(e); throw ise; } } private static class ExtendedBuffer { private String context = null; private Boolean print = null; private boolean defaultPrint = false; private StringWriter buffer = new StringWriter(); private PrintWriter writer = new PrintWriter(buffer); ExtendedBuffer(String context) { this.context = context; } boolean isPrint() { if (print == null) { return defaultPrint; } return print.booleanValue(); } void setPrint(boolean print) { this.print = Boolean.valueOf(print); } void setDefaultPrint(boolean print) { this.defaultPrint = print; } PrintWriter getWriter() { return writer; } String getContext() { return context; } public String toString() { writer.flush(); return buffer.toString(); } } /** * Prints a descriptor item's XML representation */ protected static interface ItemPrinter { /** * Print an XML representation of item to out. * @param parent the module descriptor containing item * @param item subcomponent of the descriptor, for example a {@link DependencyDescriptor} * or {@link Configuration} */ public void print(ModuleDescriptor parent, Object item, PrintWriter out); } protected static class DependencyPrinter implements ItemPrinter { public static final DependencyPrinter INSTANCE = new DependencyPrinter(); public void print(ModuleDescriptor parent, Object item, PrintWriter out) { XmlModuleDescriptorWriter.printDependency(parent, (DependencyDescriptor) item, out); } } protected static class ConfigurationPrinter implements ItemPrinter { public static final ConfigurationPrinter INSTANCE = new ConfigurationPrinter(); public void print(ModuleDescriptor parent, Object item, PrintWriter out) { XmlModuleDescriptorWriter.printConfiguration((Configuration) item, out); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy