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

org.glassfish.hk2.xml.internal.UnkeyedDiff Maven / Gradle / Ivy

There is a newer version: 4.0.0-M3
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2016-2017 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package org.glassfish.hk2.xml.internal;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.glassfish.hk2.utilities.cache.CacheUtilities;
import org.glassfish.hk2.utilities.cache.Computable;
import org.glassfish.hk2.utilities.cache.ComputationErrorException;
import org.glassfish.hk2.utilities.cache.WeakCARCache;
import org.glassfish.hk2.utilities.reflection.Logger;
import org.glassfish.hk2.xml.internal.Differences.AddData;
import org.glassfish.hk2.xml.internal.Differences.Difference;
import org.glassfish.hk2.xml.internal.Differences.MoveData;
import org.glassfish.hk2.xml.internal.Differences.RemoveData;
import org.glassfish.hk2.xml.jaxb.internal.BaseHK2JAXBBean;

/**
 * @author jwells
 *
 */
public class UnkeyedDiff {
    private final static String UNKEYED_DEBUG_PROPERTY = "org.jvnet.hk2.properties.xml.unkeyed.debug";
    private final static boolean UNKEYED_DEBUG = AccessController.doPrivileged(new PrivilegedAction() {
        @Override
        public Boolean run() {
            return Boolean.getBoolean(UNKEYED_DEBUG_PROPERTY);
        }   
    });
    
    private final List legacyList;
    private final List proposedList;
    private final ParentedModel parentModel;
    private final BaseHK2JAXBBean parent;
    
    private boolean computed = false;
    private Differences finalSolution = null;
    
    private HashMap solution;
    private HashSet usedLegacy;
    private HashSet unusedLegacy;
    private HashMap proposedAdds;
    private HashMap quantumSolutions;
    
    public UnkeyedDiff(List legacy, List proposed, BaseHK2JAXBBean parent, ParentedModel parentModel) {
        if (legacy == null) legacy = Collections.emptyList();
        if (proposed == null) proposed = Collections.emptyList();
        
        this.legacyList = new ArrayList(legacy);
        this.proposedList = new ArrayList(proposed);
        
        this.parent = parent;
        this.parentModel = parentModel;
    }
    
    private static List asList(Object a[]) {
        if (a == null) return Collections.emptyList();
        
        ArrayList retVal = new ArrayList(a.length);
        
        for (Object o : a) {
            retVal.add((BaseHK2JAXBBean) o);
        }
        
        return retVal;
    }
    
    public UnkeyedDiff(Object legacy[], Object proposed[], BaseHK2JAXBBean parent, ParentedModel parentModel) {
        this(asList(legacy), asList(proposed), parent, parentModel);
    }
    
    public synchronized Differences compute() {
        if (computed) return finalSolution;
        
        Differences retVal = null;
        try {
            retVal = internalCompute();
        }
        finally {
            if (retVal != null) {
                finalSolution = retVal;
            
                computed = true;
            }
        }
        
        return retVal;
    }
    
    private Differences internalCompute() {
        Differences retVal = new Differences();
        
        boolean needsChangeOfList = false;
        
        DifferenceTable table = new DifferenceTable();
        
        // First step, calculate the diagonal
        boolean diagonalChange = !table.calculateDiagonal();
        if (!diagonalChange) {
            if (legacyList.size() == proposedList.size()) {
                // Exactly the same lists
                return retVal;
            }
            
            // We are either adding to the end or removing from the end
            if (legacyList.size() < proposedList.size()) {
                // Adds to the end
                for (int lcv = legacyList.size(); lcv < proposedList.size(); lcv++) {
                    Difference d = new Difference(parent);
                    
                    d.addAdd(parentModel.getChildXmlTag(), new AddData(proposedList.get(lcv), lcv));
                    
                    retVal.addDifference(d);
                }
                
                return retVal;
            }
            
            // Removes from the end, proposed.size < legacy.size
            for (int lcv = proposedList.size(); lcv < legacyList.size(); lcv++) {
                String xmlTag = parentModel.getChildXmlTag();
                
                Difference d = new Difference(parent);
                
                d.addRemove(xmlTag, new RemoveData(xmlTag, lcv, legacyList.get(lcv)));
                
                retVal.addDifference(d);
            }
            
            return retVal;
        }
        
        initializeSolution();
        
        // Step one, find all diagonals that are exact, always prefer them over anything else
        for (int lcv = 0; lcv < table.getDiagonalSize(); lcv++) {
            Differences differences = table.getDiff(lcv, lcv);
            
            if (differences.getDifferences().isEmpty()) {
                // This is the best solution for this slot
                addSolution(lcv, lcv, differences);
            }
        }
        
        // Step two, match up any other exact matches
        for (int proposedIndex = 0; proposedIndex < proposedList.size(); proposedIndex++) {
            if (solution.containsKey(proposedIndex)) {
                if (UNKEYED_DEBUG) {
                    Logger.getLogger().debug("Skipping proposedIndex " + proposedIndex + " since it has already has a solution");
                }
                
                continue;
            }
            
            int currentBestDiffIndex = -1;
            Differences currentBestDiffs = null;
            for (int legacyIndex = 0; legacyIndex < legacyList.size(); legacyIndex++) {
                if (usedLegacy.contains(legacyIndex)) {
                    if (UNKEYED_DEBUG) {
                        Logger.getLogger().debug("Skipping legacyIndex " + legacyIndex + " for proposedIndex " +
                                proposedIndex + " since it has already has already been used");
                    }
                    
                    continue;
                }
                
                Differences currentDiffs = table.getDiff(legacyIndex, proposedIndex);
                
                if (currentBestDiffs == null || (currentBestDiffs.getDifferenceCost() > currentDiffs.getDifferenceCost())) {
                    currentBestDiffs = currentDiffs;
                    currentBestDiffIndex = legacyIndex;
                    
                    if (currentDiffs.getDifferences().isEmpty()) {
                        needsChangeOfList = true;
                        break;
                    }
                }
            }
            
            if (currentBestDiffs == null) {
                // We need to add this proposed bean
                Differences addMeDifference = new Differences();
                Difference difference = new Difference(parent);
                difference.addAdd(parentModel.getChildXmlTag(), new AddData(proposedList.get(proposedIndex), proposedIndex));
                
                addMeDifference.addDifference(difference);
                
                needsChangeOfList = true;
                addSolution(-1, proposedIndex, addMeDifference);
            }
            else {
                if (currentBestDiffs.getDifferences().isEmpty()) {
                    // Will never get better than a move
                    needsChangeOfList = true;
                    
                    Differences moveMeDifferences = new Differences();
                    Difference difference = new Difference(parent);
                    difference.addMove(parentModel.getChildXmlTag(), new MoveData(currentBestDiffIndex, proposedIndex));
                    
                    moveMeDifferences.addDifference(difference);
                    
                    addSolution(currentBestDiffIndex, proposedIndex, moveMeDifferences);
                }
                else {
                    BaseHK2JAXBBean currentProposed = proposedList.get(proposedIndex);
                    
                    Differences proposedAdd = proposedAdds.get(proposedIndex);
                    if (proposedAdd == null) {
                        proposedAdd = new Differences();
                        Difference d = new Difference(parent);
                        d.addAdd(parentModel.getChildXmlTag(), new AddData(currentProposed, proposedIndex));
                        
                        proposedAdd.addDifference(d);
                        
                        proposedAdds.put(proposedIndex, proposedAdd);
                    }
                    
                    SchrodingerSolution sd = new SchrodingerSolution(currentBestDiffIndex, currentBestDiffs, proposedAdd);
                    quantumSolutions.put(proposedIndex, sd);
                }
            }
            
        }
        
        for (Map.Entry entry : quantumSolutions.entrySet()) {
            int proposedIndex = entry.getKey();
            SchrodingerSolution sd = entry.getValue();
            
            int legacyIndex = sd.legacyIndexOfDiff;
            if (usedLegacy.contains(legacyIndex)) {
                // No choice, the old best solution is no longer available
                needsChangeOfList = true;
                addSolution(-1, proposedIndex, sd.proposedAddDifference);
            }
            else if (unusedLegacy.contains(legacyIndex)) {
                // The cost of the add solution now must also contain the cost of the remove of the unused
                BaseHK2JAXBBean legacy = legacyList.get(legacyIndex);
                
                int removeCost = Utilities.calculateAddCost(legacy);
                
                int totalAddCost = removeCost + sd.proposedAddDifference.getDifferenceCost();
                
                if (totalAddCost >= sd.legacyDifference.getDifferenceCost()) {
                    // Change is better
                    addSolution(legacyIndex, proposedIndex, sd.legacyDifference);
                }
                else {
                    needsChangeOfList = true;
                    addSolution(-1, proposedIndex, sd.proposedAddDifference);
                }
            }
            else {
                // Can I get here?
                throw new AssertionError("Should not be able to get here");
            }
        }
        
        // Add all the changes
        for (Differences diffs : solution.values()) {
            retVal.merge(diffs);
        }
        
        // Now add all the removes
        for (Integer legacyRemoveIndex : unusedLegacy) {
            Difference d = new Difference(parent);
            
            d.addRemove(parentModel.getChildXmlTag(),
                    new RemoveData(parentModel.getChildXmlTag(), legacyRemoveIndex, legacyList.get(legacyRemoveIndex)));
            
            retVal.addDifference(d);
        }
        
        return retVal;
    }
    
    private void initializeSolution() {
        solution = new HashMap();
        usedLegacy = new HashSet();
        unusedLegacy = new HashSet();
        for (int lcv = 0; lcv < legacyList.size(); lcv++) {
            unusedLegacy.add(lcv);
        }
        
        proposedAdds = new HashMap();
        quantumSolutions = new HashMap();
    }
    
    private void addSolution(int legacyIndex, int proposedIndex, Differences minimum) {
        solution.put(proposedIndex, minimum);
        if (legacyIndex >= 0) {
            usedLegacy.add(legacyIndex);
            unusedLegacy.remove(legacyIndex);
        }
    }
    
    private class DifferenceTable {
        private final int min;
        
        private final WeakCARCache table;
        
        private DifferenceTable() {
            min = Math.min(legacyList.size(), proposedList.size());
            
            int tableSize = legacyList.size() * proposedList.size();
            
            table = CacheUtilities.createWeakCARCache(new DifferenceMaker(),
                    tableSize, false);
        }
        
        private boolean calculateDiagonal() {
            boolean theSame = true;
            
            for (int lcv = 0; lcv < min; lcv++) {
                TableKey diagonalKey = new TableKey(lcv, lcv);
                
                Differences differences = table.compute(diagonalKey);
                if (!differences.getDifferences().isEmpty()) {
                    theSame = false;
                }
            }
            
            return theSame;
        }
        
        private Differences getDiff(int legacyIndex, int proposedIndex) {
            return table.compute(new TableKey(legacyIndex, proposedIndex));
        }
        
        private int getDiagonalSize() {
            return min;
        }
        
        @Override
        public String toString() {
            return "DifferenceTable(min=" + min + " legacySize=" + legacyList.size() + " proposedSize=" + proposedList.size() + "," + System.identityHashCode(this) + ")";
        }
    }
    
    private class DifferenceMaker implements Computable {

        @Override
        public Differences compute(TableKey key)
                throws ComputationErrorException {
            BaseHK2JAXBBean legacyBean = legacyList.get(key.legacyIndex);
            BaseHK2JAXBBean proposedBean = proposedList.get(key.proposedIndex);
            
            Differences retVal = Utilities.getDiff(legacyBean, proposedBean);
            return retVal;
        }
        
        @Override
        public String toString() {
            return "DifferenceMaker(" + System.identityHashCode(this) + ")";
        }
    }
    
    private static class TableKey {
        private final int legacyIndex;
        private final int proposedIndex;
        private final int hash;
        
        private TableKey(int legacyIndex, int proposedIndex) {
            this.legacyIndex = legacyIndex;
            this.proposedIndex = proposedIndex;
            hash = legacyIndex ^ proposedIndex;
        }
        
        @Override
        public int hashCode() {
            return hash;
        }
        
        @Override
        public boolean equals(Object o) {
            if (o == null) return false;
            if (!(o instanceof TableKey)) return false;
            
            TableKey other = (TableKey) o;
            
            return (other.legacyIndex == legacyIndex) && (other.proposedIndex == proposedIndex);
        }
        
        @Override
        public String toString() {
            return "TableKey(" + legacyIndex + "," + proposedIndex + "," + System.identityHashCode(this) + ")";
        }
    }
    
    /**
     * Schrodinger solution because it isn't one or the other until later
     * when we know more about the full solution
     * 
     * @author jwells
     *
     */
    private static class SchrodingerSolution {
        /*
         * Solution 0:  The legacy modification
         */
        private final int legacyIndexOfDiff;
        private final Differences legacyDifference;
        
        /*
         * Solution 1:  The proposed add modification
         */
        private final Differences proposedAddDifference;
        
        private SchrodingerSolution(int legacyIndexOfDiff, Differences legacyDifference, Differences proposedAddDifference) {
            this.legacyIndexOfDiff = legacyIndexOfDiff;
            this.legacyDifference = legacyDifference;
            
            this.proposedAddDifference = proposedAddDifference;
        }
        
        @Override
        public String toString() {
            return "SchrodingerSolution(" + legacyIndexOfDiff + "," + legacyDifference + "," + proposedAddDifference + "," + System.identityHashCode(this) + ")";
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy