com.igormaznitsa.upom.UPomModel.java Source code

Java tutorial

Introduction

Here is the source code for com.igormaznitsa.upom.UPomModel.java

Source

/* 
 * Copyright 2015 Igor Maznitsa (http://www.igormaznitsa.com).
 *
 * Licensed 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 com.igormaznitsa.upom;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.io.IOUtils;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
import org.apache.maven.model.merge.ModelMerger;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

@SuppressWarnings({ "unchecked", "rawtypes" })
public final class UPomModel {

    private static final String MAVEN_MODEL_PACKAGE_PREFIX = "org.apache.maven.model.";

    private static final String UBER_POM_GROUPID = "com.igormaznitsa";
    private static final String UBER_POM_ARTIFACTID = "uber-pom";

    private final Model model;
    private final Map<String, Object> savedValues = new HashMap<String, Object>();

    public UPomModel(final File modelFile) throws Exception {
        final FileInputStream in = new FileInputStream(modelFile);
        try {
            final MavenXpp3Reader reader = new MavenXpp3Reader();
            this.model = reader.read(in, true);
        } finally {
            IOUtils.closeQuietly(in);
        }
    }

    public UPomModel(final Model pom) {
        this.model = pom.clone();
    }

    private static Node findFirstElement(final Node node) {
        if (node == null) {
            return null;
        }
        Node result = node.getFirstChild();
        while (result != null && result.getNodeType() != Node.ELEMENT_NODE) {
            result = result.getNextSibling();
        }
        return result;
    }

    private static Node nextSiblingElement(final Node node) {
        if (node == null) {
            return null;
        }
        Node next = node.getNextSibling();
        while (next != null && next.getNodeType() != Node.ELEMENT_NODE) {
            next = next.getNextSibling();
        }
        return next;
    }

    private static void insideElementJanitor(final Log log, final Node node, final List<String> path) {
        path.add(node.getNodeName());
        Node element = findFirstElement(node);
        while (element != null) {
            duplicatedSiblingJanitor(log, element, path);
            element = nextSiblingElement(element);
        }
        path.remove(path.size() - 1);
    }

    private static String pathToString(final List<String> path) {
        final StringBuilder result = new StringBuilder();
        for (final String s : path) {
            if (result.length() > 0) {
                result.append('/');
            }
            result.append(s);
        }
        return result.toString();
    }

    private static void duplicatedSiblingJanitor(final Log log, final Node node, final List<String> path) {
        if (node == null) {
            return;
        }

        insideElementJanitor(log, node, path);
        Node sibling = nextSiblingElement(node);
        while (sibling != null) {
            insideElementJanitor(log, sibling, path);
            sibling = nextSiblingElement(sibling);
        }

        sibling = nextSiblingElement(node);
        while (sibling != null) {
            if (node.isEqualNode(sibling)) {
                path.add(node.getNodeName());
                final Node deleting = sibling;
                sibling = nextSiblingElement(sibling);
                if (log != null) {
                    log.warn("Removing duplicated element : " + pathToString(path));
                }
                deleting.getParentNode().removeChild(deleting);
                path.remove(path.size() - 1);
            } else {
                sibling = nextSiblingElement(sibling);
            }
        }
    }

    private static String findAndRemoveDuplicatedSiblings(final Log log, final String xmlText) throws Exception {
        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        final DocumentBuilder builder = factory.newDocumentBuilder();
        final Document xmldoc = builder.parse(new InputSource(new StringReader(xmlText)));

        insideElementJanitor(log, xmldoc, new ArrayList<String>());

        final TransformerFactory tFactory = TransformerFactory.newInstance();
        final Transformer transformer = tFactory.newTransformer();
        final DOMSource source = new DOMSource(xmldoc);
        final StringWriter buffer = new StringWriter(xmlText.length());
        final StreamResult result = new StreamResult(buffer);
        transformer.transform(source, result);
        return ((StringWriter) result.getWriter()).toString();
    }

    public String asXML(final Log log, final boolean removeDuplicatedSiblings) throws Exception {
        final MavenXpp3Writer mavenWritter = new MavenXpp3Writer();
        final StringWriter buffer = new StringWriter(16384);
        mavenWritter.write(buffer, this.model);
        String result = buffer.toString();
        if (removeDuplicatedSiblings) {
            if (log != null) {
                log.warn("Activated search and removing of duplicated sibling elements!");
            }
            result = findAndRemoveDuplicatedSiblings(log, result);
        } else if (log != null) {
            log.info("Search and removing of duplicated sibling elements is OFF");
        }
        return result;
    }

    public void saveState(final String... keepPaths) throws Exception {
        this.savedValues.clear();
        for (final String p : keepPaths) {
            this.savedValues.put(p, this.processPathStepToGet(splitPath(p), 0, this.model));
        }
    }

    public void restoreState() throws Exception {
        for (final Map.Entry<String, Object> e : this.savedValues.entrySet()) {
            this.processPathStepToSet(splitPath(e.getKey()), 0, this.model, e.getValue());
        }
    }

    public void restoreStateFrom(final UPomModel model) throws Exception {
        for (final Map.Entry<String, Object> e : model.savedValues.entrySet()) {
            this.processPathStepToSet(splitPath(e.getKey()), 0, this.model, e.getValue());
        }
    }

    public Model getModel() {
        return this.model;
    }

    public UPomModel merge(final UPomModel other) throws Exception {
        final ModelMerger merger = new ModelMerger();
        merger.merge(this.model, other.model, true, null);
        return this;
    }

    public void removeSelf() {
        if (this.model.getBuild() == null)
            throw new UPomException("Cannot find plugin definitions in POM");

        this.model.getBuild().removePlugin(this.getUberPomPlugin());
    }

    private Plugin getUberPomPlugin() {
        for (Plugin plugin : this.model.getBuild().getPlugins()) {
            if (UBER_POM_GROUPID.equals(plugin.getGroupId()) && UBER_POM_ARTIFACTID.equals(plugin.getArtifactId()))
                return plugin;
        }
        throw new UPomException("Cannot find uber-pom plugin definition");
    }

    public boolean remove(final String removePath) throws Exception {
        return processPathStepToSet(splitPath(removePath), 0, this.model, null);
    }

    public void set(final String path, final String value) throws Exception {
        this.processPathStepToSet(splitPath(path), 0, this.model, value);
    }

    public Object get(final String path) throws Exception {
        return this.processPathStepToGet(splitPath(path), 0, this.model);
    }

    private static Method findMethod(final Class klazz, final String methodName, final boolean onlyPublic) {
        Method result = null;
        for (final Method m : klazz.getMethods()) {
            if (onlyPublic && !Modifier.isPublic(m.getModifiers())) {
                continue;
            }
            if (m.getName().equalsIgnoreCase(methodName)) {
                result = m;
                break;
            }
        }
        return result;
    }

    private static Field findDeclaredFieldForName(final Class klazz, final String fieldName) {
        Field result = null;
        Class curr = klazz;
        while (curr.getName().startsWith(MAVEN_MODEL_PACKAGE_PREFIX)) {
            for (final Field m : curr.getDeclaredFields()) {
                if (m.getName().equalsIgnoreCase(fieldName)) {
                    result = m;
                    break;
                }
            }
            if (result != null) {
                result.setAccessible(true);
                break;
            }
            curr = klazz.getSuperclass();
        }
        return result;
    }

    private static String makePathStr(final String[] path, final int toIndex) {
        final StringBuilder result = new StringBuilder();
        for (int i = 0; i <= toIndex; i++) {
            if (result.length() > 0) {
                result.append('/');
            }
            result.append(path[i]);
        }
        return result.toString();
    }

    private static Collection cloneCollection(final Collection collection) throws Exception {
        final Class collectionClass = collection.getClass();
        final Constructor constructor = collectionClass.getConstructor(Collection.class);
        return (Collection) constructor.newInstance(collection);
    }

    private static Map cloneMap(final Map map) throws Exception {
        final Class mapClass = map.getClass();
        final Constructor constructor = mapClass.getConstructor(Map.class);
        return (Map) constructor.newInstance(map);
    }

    private static Object ensureCloning(final Object obj) throws Exception {
        if (obj == null) {
            return null;
        }
        final Method clone = findMethod(obj.getClass(), "clone", true);
        final Object result;
        if (clone == null) {
            if (obj instanceof Map) {
                result = cloneMap((Map) obj);
            } else if (obj instanceof Collection) {
                result = cloneCollection((Collection) obj);
            } else {
                result = obj;
            }
        } else {
            result = clone.invoke(obj);
        }
        return result;
    }

    private static void setField(final Object instance, final Field field, final Object value) throws Exception {
        final Object currentValue = field.get(instance);
        if (currentValue == null) {
            if (value != null) {
                field.setAccessible(true);
                field.set(instance, value);
            }
        } else if (currentValue instanceof Map) {
            ((Map) currentValue).clear();
            if (value != null) {
                ((Map) currentValue).putAll((Map) value);
            }
        } else if (currentValue instanceof Collection) {
            ((Collection) currentValue).clear();
            if (value != null) {
                ((Collection) currentValue).addAll((Collection) value);
            }
        } else {
            field.set(instance, ensureCloning(value));
        }
    }

    private static Object getField(final Object instance, final Field field) throws Exception {
        return ensureCloning(field.get(instance));
    }

    private boolean processPathStepToSet(final String[] path, final int pathStart, final Object instance,
            final Object value) throws Exception {
        final String fieldName = path[pathStart];

        if (pathStart == path.length - 1) {
            // last step
            // find setter
            final Method setter = findMethod(instance.getClass(), "set" + fieldName, true);
            if (setter == null) {
                throw new UPomException("Can't find model field '" + makePathStr(path, pathStart) + '\'');
            }

            final Class[] params = setter.getParameterTypes();
            if (params.length == 0) {
                throw new UPomException("Detected zero setter '" + makePathStr(path, pathStart) + "\'");
            } else if (params.length == 1) {
                setter.invoke(instance, ensureCloning(value));
            } else {
                final Field field = findDeclaredFieldForName(instance.getClass(), fieldName);
                if (field != null) {
                    setField(instance, field, value);
                } else {
                    throw new UPomException("Unsupported type for '" + makePathStr(path, pathStart) + "\'");
                }
            }
            return true;
        } else {
            // find getter
            final Method getter = findMethod(instance.getClass(), "get" + fieldName, true);
            if (getter == null) {
                throw new UPomException("Can't find model field '" + makePathStr(path, pathStart) + '\'');
            }
            final Object nextInstance = getter.invoke(instance);
            if (nextInstance == null) {
                return false;
            }

            final boolean theNextPathItemIsLastOne = path.length - 1 == pathStart + 1;
            if (nextInstance instanceof Collection) {
                final Type returnType = getter.getGenericReturnType();
                if (returnType instanceof ParameterizedType) {
                    final ParameterizedType paramType = (ParameterizedType) returnType;
                    final Type[] argTypes = paramType.getActualTypeArguments();

                    if (theNextPathItemIsLastOne) {
                        if (value == null) {
                            ((Collection) nextInstance).clear();
                            return true;
                        } else if (value instanceof Collection) {
                            ((Collection) nextInstance).clear();
                            for (final Object obj : ((Collection) value)) {
                                ((Collection) nextInstance).add(obj);
                            }
                            return true;
                        } else {
                            ((Collection) nextInstance).clear();
                            return ((Collection) nextInstance).add(value);
                        }
                    }

                    final String nextPathItem = path[pathStart + 1].toLowerCase(Locale.ENGLISH);
                    if (argTypes[0].toString().toLowerCase(Locale.ENGLISH).endsWith(nextPathItem)) {
                        boolean result = false;
                        for (final Object collectionItem : (Collection) nextInstance) {
                            result |= processPathStepToSet(path, pathStart + 2, collectionItem, value);
                        }
                        return result;
                    } else {
                        throw new UPomException(
                                "Collection element type is not '" + makePathStr(path, pathStart + 1) + '\'');
                    }
                } else {
                    throw new UPomException("Can't find model field '" + makePathStr(path, pathStart) + '\'');
                }
            } else if (nextInstance instanceof Map) {
                final Map map = (Map) nextInstance;
                final String nextPathItem = path[pathStart + 1];
                if (theNextPathItemIsLastOne) {
                    if (value == null) {
                        map.remove(nextPathItem);
                    } else {
                        map.put(nextPathItem, value);
                    }
                    return true;
                } else {
                    return map.containsKey(nextPathItem)
                            ? processPathStepToSet(path, pathStart + 2, map.get(nextPathItem), value)
                            : false;
                }
            } else {
                return processPathStepToSet(path, pathStart + 1, nextInstance, value);
            }
        }
    }

    private Object processPathStepToGet(final String[] path, final int pathStart, final Object instance)
            throws Exception {
        final String fieldName = path[pathStart];

        if (pathStart == path.length - 1) {
            // last step
            // find getter
            final Method getter = findMethod(instance.getClass(), "get" + fieldName, true);
            if (getter == null) {
                throw new UPomException("Can't find model field '" + makePathStr(path, pathStart) + '\'');
            }

            final Class[] params = getter.getParameterTypes();
            if (params.length == 0) {
                return ensureCloning(getter.invoke(instance));
            } else {
                final Field field = findDeclaredFieldForName(instance.getClass(), fieldName);
                if (field != null) {
                    return getField(instance, field);
                } else {
                    throw new UPomException("Unsupported type for '" + makePathStr(path, pathStart) + "\'");
                }
            }
        } else {
            // find getter
            final Method getter = findMethod(instance.getClass(), "get" + fieldName, true);
            if (getter == null) {
                throw new UPomException("Can't find model field '" + makePathStr(path, pathStart) + '\'');
            }
            final Object nextInstance = getter.invoke(instance);
            if (nextInstance == null) {
                return false;
            }

            final boolean theNextPathItemIsLastOne = path.length - 1 == pathStart + 1;

            if (nextInstance instanceof Collection) {
                final Type returnType = getter.getGenericReturnType();
                if (returnType instanceof ParameterizedType) {
                    final ParameterizedType paramType = (ParameterizedType) returnType;
                    final Type[] argTypes = paramType.getActualTypeArguments();

                    if (theNextPathItemIsLastOne) {
                        // take only the first value
                        return ((Collection) nextInstance).isEmpty() ? null
                                : ((Collection) nextInstance).iterator().next();
                    }

                    final String nextPathItem = path[pathStart + 1].toLowerCase(Locale.ENGLISH);
                    if (argTypes[0].toString().toLowerCase(Locale.ENGLISH).endsWith(nextPathItem)) {
                        return ((Collection) nextInstance).isEmpty() ? null
                                : processPathStepToGet(path, pathStart + 2,
                                        ((Collection) nextInstance).iterator().next());
                    } else {
                        throw new UPomException(
                                "Collection element type is not '" + makePathStr(path, pathStart + 1) + '\'');
                    }
                } else {
                    throw new UPomException("Can't find model field '" + makePathStr(path, pathStart) + '\'');
                }
            } else if (nextInstance instanceof Map) {
                final Map map = (Map) nextInstance;
                final String nextPathItem = path[pathStart + 1];
                if (theNextPathItemIsLastOne) {
                    return map.get(nextPathItem);
                } else {
                    return map.containsKey(nextPathItem)
                            ? processPathStepToGet(path, pathStart + 2, map.get(nextPathItem))
                            : null;
                }
            } else {
                return processPathStepToGet(path, pathStart + 1, nextInstance);
            }
        }
    }

    public void injectIntoProject(final Log log, final MavenProject project) throws Exception {
        for (final Method setter : project.getClass().getMethods()) {
            final String methodName = setter.getName();
            final Class<?>[] setterParams = setter.getParameterTypes();
            if (setterParams.length == 1 && methodName.startsWith("set")) {
                final String paramName = methodName.substring(3).toLowerCase(Locale.ENGLISH);
                if (paramName.equals("build")) {
                    continue;
                }

                Method getter = null;
                for (final Method g : this.model.getClass().getMethods()) {
                    final Class<?>[] getterParams = g.getParameterTypes();
                    if (getterParams.length == 0 && g.getName().equalsIgnoreCase("get" + paramName)) {
                        getter = g;
                        break;
                    }
                }
                if (getter != null && setterParams[0].isAssignableFrom(getter.getReturnType())) {
                    final Object value = getter.invoke(this.model);
                    if (value == null) {
                        log.debug(getter.getName() + "() X-> " + setter.getName() + "()");
                    } else {
                        log.debug(getter.getName() + "() --> " + setter.getName() + "()");
                        setter.invoke(project, getter.invoke(this.model));
                    }
                }
            }
        }
    }

    private static String[] splitPath(final String path) {
        final String[] result = path.trim().split("\\/");
        return result;
    }

    public void assignTo(final MavenProject project) {
        project.setOriginalModel(this.model);
    }

    @Override
    public String toString() {
        return this.model.toString();
    }

}