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

org.spdx.compare.SpdxComparer Maven / Gradle / Ivy

/**
 * Copyright (c) 2013 Source Auditor Inc.
 * Copyright (c) 2013 Black Duck Software Inc.
 *
 *   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 org.spdx.compare;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.spdx.rdfparser.InvalidSPDXAnalysisException;
import org.spdx.rdfparser.SPDXCreatorInformation;
import org.spdx.rdfparser.SPDXReview;
import org.spdx.rdfparser.SpdxPackageVerificationCode;
import org.spdx.rdfparser.license.AnyLicenseInfo;
import org.spdx.rdfparser.license.ExtractedLicenseInfo;
import org.spdx.rdfparser.model.Annotation;
import org.spdx.rdfparser.model.Checksum;
import org.spdx.rdfparser.model.ExternalDocumentRef;
import org.spdx.rdfparser.model.RdfModelObject;
import org.spdx.rdfparser.model.Relationship;
import org.spdx.rdfparser.model.SpdxDocument;
import org.spdx.rdfparser.model.SpdxElement;
import org.spdx.rdfparser.model.SpdxFile;
import org.spdx.rdfparser.model.SpdxItem;
import org.spdx.rdfparser.model.SpdxPackage;
import org.spdx.rdfparser.model.SpdxSnippet;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * Performs a comparison between two or more SPDX documents and holds the results of the comparison
 * The main function to perform the comparison is compare(spdxdoc1, spdxdoc2)
 * 
 * For reviewers, the comparison results are separated into unique reviewers for a give document
 * which can be obtained by the method getUniqueReviewers(index1, index2).  The 
 * uniqueness is determined by the reviewer name.  If two documents contain reviewers with the 
 * same name but different dates or comments, the reviews are considered to be the same review
 * with different data.  The differences for these reviews can be obtained through the method
 * getReviewerDifferences(index1, index2)
 * 
 * For files, the comparison results are separated into unique files based on the file names
 * which can be obtained by the method getUniqueFiles(index1, index2).  If two
 * documents contain files with the same name, but different data, the differences for these
 * files can be obtained through the method getFileDifferences(index1, index2)
 * 
 * Multi-threading considerations: This class is "mostly" threadsafe in that the calls to 
 * perform the comparison are synchronized and a flag is used to throw an error for any
 * calls to getters when a compare is in progress.  There is a small theoretical window in the
 * getters where the compare operation is started in the middle of a get operation.
 * 
 * @author Gary O'Neall
 *
 */
public class SpdxComparer {
	
	/**
	 * Contains the results of a comparison between two SPDXReviews where
	 * the reviewer name is the same but there is a difference in the
	 * reviewer comment or the reviewer date
	 * @author Gary O'Neall
	 *
	 */
	public static class SPDXReviewDifference {
		
		boolean commentsEqual;
		boolean datesEqual;
		String comment1;
		String comment2;
		String date1;
		String date2;
		String reviewer;

		/**
		 * @param spdxReview
		 * @param spdxReview2
		 */
		public SPDXReviewDifference(SPDXReview spdxReview,
				SPDXReview spdxReview2) {
			commentsEqual = spdxReview.getComment().trim().equals(spdxReview2.getComment().trim());
			datesEqual = spdxReview.getReviewDate().equals(spdxReview2.getReviewDate());
			this.comment1 = spdxReview.getComment();
			this.comment2 = spdxReview2.getComment();
			this.date1 = spdxReview.getReviewDate();
			this.date2 = spdxReview2.getReviewDate();
			this.reviewer = spdxReview.getReviewer();
		}

		/**
		 * @return true of the dates are equal
		 */
		public boolean isDateEqual() {
			return this.datesEqual;
		}

		/**
		 * @return
		 */
		public String getReviewer() {
			return this.reviewer;
		}

		/**
		 * Get the reviewer date for one of the two reviews compared
		 * @param i if 0, the review date of the first reviewer, if 1, it is the second reviewer
		 * @return
		 * @throws SpdxCompareException 
		 */
		public String getDate(int i) throws SpdxCompareException {
			if (i == 0) {
				return this.date1;
			} else if (i == 1) {
				return this.date2;
			} else {
				throw(new SpdxCompareException("Invalid index for get reviewer date"));
			}
		}

		/**
		 * @return true if comments are equal
		 */
		public boolean isCommentEqual() {
			return this.commentsEqual;
		}

		/**
		 * Get the reviewer comment for one of the two reviews compared
		 * @param i if 0, the review date of the first reviewer, if 1, it is the second reviewer
		 * @return
		 * @throws SpdxCompareException 
		 */
		public String getComment(int i) throws SpdxCompareException {
			if (i == 0) {
				return this.comment1;
			} else if (i == 1) {
				return this.comment2;
			} else {
				throw(new SpdxCompareException("Invalid index for get reviewer date"));
			}
		}
		
	}
	

	
	private SpdxDocument[] spdxDocs = null;
	private boolean differenceFound = false;
	private boolean compareInProgress = false;
	
	// Document level results
	private boolean spdxVersionsEqual = true;
	private boolean documentCommentsEqual = true;
	private boolean dataLicenseEqual = true;
	private boolean licenseListVersionEquals = true;
	private boolean documentContentsEquals = true;
	
	// Reviewer results
	/**
	 * Holds a map of all SPDX documents which have reviewers unique relative to other SPDX document
	 * based on the reviewer name.  The results of the map is another map of all SPDX documents in 
	 * the comparison which do not contain some of the reviewers in the key document.  See the
	 * implementation of compareReviewers for details
	 */
	private Map> uniqueReviews = Maps.newHashMap();
	
	/**
	 * Holds a map of any SPDX documents which have reviewer differenes.  A reviewer difference
	 * is an SPDXReview with the same reviewer name but a different reviewer date or comment
	 */
	private Map> reviewerDifferences = Maps.newHashMap();
	
	// Extracted Licensing Info results
	/**
	 * Holds a map of all SPDX documents which have extracted license infos unique relative to other SPDX document
	 * based on the reviewer name.  The results of the map is another map of all SPDX documents in 
	 * the comparison which do not contain some of the reviewers in the key document.  See the
	 * implementation of compareReviewers for details
	 */
	private Map> uniqueExtractedLicenses = Maps.newHashMap();
	/**
	 * Map of any SPDX documents that have extraced license infos with equivalent text but different comments, id's or other fields
	 */
	private Map> licenseDifferences = Maps.newHashMap();
	/**
	 * Maps the license ID's for the extracted license infos of the documents being compared.  License ID's are mapped based on the text
	 * being equivalent 
	 */
	private Map>> extractedLicenseIdMap = Maps.newHashMap();

	private boolean creatorInformationEquals;
	
	private Map> uniqueCreators = Maps.newHashMap();
	
	// file compare results
	/**
	 * Holds a map of all SPDX documents which have files unique relative to other SPDX document
	 * based on the file name.  The results of the map is another map of all SPDX documents in 
	 * the comparison which do not contain some of the files in the key document.  See the
	 * implementation of compareFiles for details
	 */
	private Map> uniqueFiles = Maps.newHashMap();
	
	/**
	 * Holds a map of any SPDX documents which have file differences.  A file difference
	 * is an SPDXReview with the same filename name but a different file property
	 */
	private Map> fileDifferences = Maps.newHashMap();

	// Package compare results
	/**
	 * Holds a map of all SPDX documents which have packages unique relative to other SPDX document
	 * based on the package name and package version.  The results of the map is another map of all SPDX documents in 
	 * the comparison which do not contain some of the packages in the key document.  See the
	 * implementation of comparePackages for details
	 */
	private Map> uniquePackages = Maps.newHashMap();
	
	/**
	 * Map of package names to package comparisons
	 */
	private Map packageComparers = Maps.newHashMap();
	
	// Annotation comparison results
	private Map> uniqueDocumentAnnotations = Maps.newHashMap();

	// Document Relationships comparison results
	private Map> uniqueDocumentRelationships = Maps.newHashMap();

	// External Document References comparison results
	private Map> uniqueExternalDocumentRefs = Maps.newHashMap();
	
	// Snippet references comparison results
	private Map> uniqueSnippets = Maps.newHashMap();
	private Map  snippetComparers = Maps.newHashMap();
	
	public SpdxComparer() {
		
	}
	
	/**
	 * Compares 2 SPDX documents
	 * @param spdxDoc1
	 * @param spdxDoc2
	 * @throws InvalidSPDXAnalysisException
	 * @throws SpdxCompareException
	 */
	public void compare(SpdxDocument spdxDoc1, SpdxDocument spdxDoc2) throws InvalidSPDXAnalysisException, SpdxCompareException {
		compare(new SpdxDocument[] {spdxDoc1, spdxDoc2});
	}
	
	/**
	 * Compares multiple SPDX documents
	 * @param spdxDocuments
	 * @throws SpdxCompareException 
	 * @throws InvalidSPDXAnalysisException 
	 */
	public synchronized void compare(SpdxDocument[] spdxDocuments) throws InvalidSPDXAnalysisException, SpdxCompareException {
		//TODO: Add a monitor function which allows for cancel
		clearCompareResults();
		this.spdxDocs = spdxDocuments;
		differenceFound = false;
		performCompare();	
	}

	/**
	 * @throws InvalidSPDXAnalysisException 
	 * @throws SpdxCompareException 
	 * 
	 */
	private void performCompare() throws InvalidSPDXAnalysisException, SpdxCompareException {
		compareInProgress = true;
		differenceFound = false;
		compareExtractedLicenseInfos();	// note - this must be done first to build the translation map of IDs
		compareDocumentFields();
		compareSnippets();
		compareFiles();
		comparePackages();
		compareReviewers();
		compareCreators();
		compareDocumentAnnotations();
		compareDocumentRelationships();
		compareExternalDocumentRefs();
		compareInProgress = false;	
	}

	/**
	 * Compare the snippets in the documents
	 * @throws SpdxCompareException 
	 */
	private void compareSnippets() throws SpdxCompareException {
		// This will be a complete NXN comparison of all documents filling in the uniqueSnippets map
		if (this.spdxDocs == null || this.spdxDocs.length < 1) {
			return;
		}
		this.uniqueSnippets.clear();
		this.snippetComparers.clear();
		// N x N comparison of all snippets
		for (int i = 0; i < spdxDocs.length; i++) {
			List snippetsA;
			try {
				snippetsA = spdxDocs[i].getDocumentContainer().findAllSnippets();
			} catch (InvalidSPDXAnalysisException e) {
				throw(new SpdxCompareException("Error collecting snippets from SPDX document "+spdxDocs[i].getName(), e));
			}
			// note - the snippet arrays MUST be sorted for the comparator methods to work
			Collections.sort(snippetsA);
			addSnippetComparers(spdxDocs[i], snippetsA, this.extractedLicenseIdMap);
			Map uniqueAMap = this.uniqueSnippets.get(spdxDocs[i]);
			if (uniqueAMap == null) {
				uniqueAMap = Maps.newHashMap();
			}
			for (int j = 0; j < spdxDocs.length; j++) {
				if (j == i) {
					continue;
				}
				List snippetsB;
				try {
					snippetsB = spdxDocs[j].getDocumentContainer().findAllSnippets();
				} catch (InvalidSPDXAnalysisException e) {
					throw(new SpdxCompareException("Error collecting snippets from SPDX document "+spdxDocs[i].getName(), e));
				}
				//Note that the files arrays must be sorted for the find methods to work
				Collections.sort(snippetsB);
				SpdxSnippet[] uniqueAB = findUniqueSnippets(snippetsA, snippetsB);
				if (uniqueAB != null && uniqueAB.length > 0) {
					uniqueAMap.put(spdxDocs[j], uniqueAB);
				}
			}
			if (!uniqueAMap.isEmpty()) {
				this.uniqueSnippets.put(spdxDocs[i], uniqueAMap);
			}
		}
		if (!_isSnippetsEqualsNoCheck()) {
			this.differenceFound = true;
		}		
	}

	/**
	 * @param snippetsA
	 * @param snippetsB
	 * @return
	 */
	private SpdxSnippet[] findUniqueSnippets(List snippetsA,
			List snippetsB) {
		int bIndex = 0;
		int aIndex = 0;
		List alRetval = Lists.newArrayList();
		while (aIndex < snippetsA.size()) {
			if (bIndex >= snippetsB.size()) {
				alRetval.add(snippetsA.get(aIndex));
				aIndex++;
			} else {
				int compareVal = snippetsA.get(aIndex).compareTo(snippetsB.get(bIndex));
				if (compareVal == 0) {
					// snippets are equal
					aIndex++;
					bIndex++;
				} else if (compareVal > 0) {
					// snippetsA is greater than snippetsB
					bIndex++;
				} else {
					// snippetsB is greater than snippetsA
					alRetval.add(snippetsA.get(aIndex));
					aIndex++;
				}
			}
		}
		return alRetval.toArray(new SpdxSnippet[alRetval.size()]);
	}

	/**
	 * @param spdxDocument
	 * @param snippets
	 * @param extractedLicenseIdMap2
	 * @throws SpdxCompareException 
	 */
	private void addSnippetComparers(
			SpdxDocument spdxDocument,
			List snippets,
			Map>> extractedLicenseIdMap2) throws SpdxCompareException {
		for (SpdxSnippet snippet:snippets) {
			SpdxSnippetComparer comparer = this.snippetComparers.get(snippet.toString());
			if (comparer == null) {
				comparer = new SpdxSnippetComparer(extractedLicenseIdMap);
				this.snippetComparers.put(snippet.toString(), comparer);
			}
			comparer.addDocumentSnippet(spdxDocument, snippet);
		}
	}

	/**
	 * @throws InvalidSPDXAnalysisException 
	 * 
	 */
	private void compareExternalDocumentRefs() throws InvalidSPDXAnalysisException {
		// this will be a N x N comparison of all external document relationships to fill the
		// hashmap uniqueExternalDocumentRefs
		for (int i = 0; i < spdxDocs.length; i++) {
			ExternalDocumentRef[] externalDocRefsA = spdxDocs[i].getExternalDocumentRefs();
			Map uniqueAMap = uniqueExternalDocumentRefs.get(spdxDocs[i]);
			if (uniqueAMap == null) {
				uniqueAMap = Maps.newHashMap();
				// We will put this into the hashmap at the end of this method if it is not empty
			}
			for (int j = 0; j < spdxDocs.length; j++) {
				if (j == i) {
					continue;	// skip comparing to ourself
				}
				ExternalDocumentRef[] externalDocRefsB = spdxDocs[j].getExternalDocumentRefs();

				// find any external refs in A that are not in B
				ExternalDocumentRef[] uniqueA = findUniqueExternalDocumentRefs(externalDocRefsA, externalDocRefsB);
				if (uniqueA != null && uniqueA.length > 0) {
					uniqueAMap.put(spdxDocs[j], uniqueA);					
				}
			}
			if (uniqueAMap.keySet().size() > 0) {
				this.uniqueExternalDocumentRefs.put(spdxDocs[i], uniqueAMap);
			}
		}
		if (!this._isExternalDcoumentRefsEqualsNoCheck()) {
			this.differenceFound = true;
		}	
	}

	/**
	 * Compare all of the document level relationships
	 */
	private void compareDocumentRelationships() {
		// this will be a N x N comparison of all document level relationships to fill the
		// hashmap uniqueDocumentRelationships
		for (int i = 0; i < spdxDocs.length; i++) {
			Relationship[] relationshipsA = spdxDocs[i].getRelationships();
			Map uniqueAMap = uniqueDocumentRelationships.get(spdxDocs[i]);
			if (uniqueAMap == null) {
				uniqueAMap = Maps.newHashMap();
				// We will put this into the hashmap at the end of this method if it is not empty
			}
			for (int j = 0; j < spdxDocs.length; j++) {
				if (j == i) {
					continue;	// skip comparing to ourself
				}
				Relationship[] relationshipsB = spdxDocs[j].getRelationships();

				// find any creators in A that are not in B
				Relationship[] uniqueA = findUniqueRelationships(relationshipsA, relationshipsB);
				if (uniqueA != null && uniqueA.length > 0) {
					uniqueAMap.put(spdxDocs[j], uniqueA);					
				}
			}
			if (uniqueAMap.keySet().size() > 0) {
				this.uniqueDocumentRelationships.put(spdxDocs[i], uniqueAMap);
			}
		}
		if (!this._isDocumentRelationshipsEqualsNoCheck()) {
			this.differenceFound = true;
		}	
	}

	/**
	 * Compare all of the Document level annotations
	 */
	private void compareDocumentAnnotations() {
		// this will be a N x N comparison of all document level annotations to fill the
		// hashmap uniqueAnnotations
		for (int i = 0; i < spdxDocs.length; i++) {
			Annotation[] annotationsA = spdxDocs[i].getAnnotations();
			Map uniqueAMap = uniqueDocumentAnnotations.get(spdxDocs[i]);
			if (uniqueAMap == null) {
				uniqueAMap = Maps.newHashMap();
				// We will put this into the hashmap at the end of this method if it is not empty
			}
			for (int j = 0; j < spdxDocs.length; j++) {
				if (j == i) {
					continue;	// skip comparing to ourself
				}
				Annotation[] annotationsB = spdxDocs[j].getAnnotations();

				// find any creators in A that are not in B
				Annotation[] uniqueA = findUniqueAnnotations(annotationsA, annotationsB);
				if (uniqueA != null && uniqueA.length > 0) {
					uniqueAMap.put(spdxDocs[j], uniqueA);					
				}
			}
			if (uniqueAMap.keySet().size() > 0) {
				this.uniqueDocumentAnnotations.put(spdxDocs[i], uniqueAMap);
			}
		}
		if (!this._isDocumentAnnotationsEqualsNoCheck()) {
			this.differenceFound = true;
		}	
	}

	/**
	 * @throws InvalidSPDXAnalysisException 
	 * @throws SpdxCompareException 
	 * 
	 */
	private void compareFiles() throws InvalidSPDXAnalysisException, SpdxCompareException {
		this.uniqueFiles.clear();
		this.fileDifferences.clear();
		// N x N comparison of all files
		for (int i = 0; i < spdxDocs.length; i++) {
			SpdxFile[] filesA = collectAllFiles(spdxDocs[i]);
			// note - the file arrays MUST be sorted for the comparator methods to work
			Arrays.sort(filesA);
			Map uniqueAMap = this.uniqueFiles.get(spdxDocs[i]);
			if (uniqueAMap == null) {
				uniqueAMap = Maps.newHashMap();
			}
			// this map will be added to uniqueFiles at the end if we find anything
			Map diffMap = this.fileDifferences.get(spdxDocs[i]);
			if (diffMap == null) {
				diffMap = Maps.newHashMap();
			}
			for (int j = 0; j < spdxDocs.length; j++) {
				if (j == i) {
					continue;
				}
				SpdxFile[] filesB = collectAllFiles(spdxDocs[j]);
				//Note that the files arrays must be sorted for the find methods to work
				Arrays.sort(filesB);
				SpdxFile[] uniqueAB = findUniqueFiles(filesA, filesB);
				if (uniqueAB != null && uniqueAB.length > 0) {
					uniqueAMap.put(spdxDocs[j], uniqueAB);
				}
				SpdxFileDifference[] differences = findFileDifferences(spdxDocs[i], spdxDocs[j], filesA, filesB, this.extractedLicenseIdMap);
				if (differences != null && differences.length > 0) {
					diffMap.put(spdxDocs[j], differences);
				}
			}
			if (!uniqueAMap.isEmpty()) {
				this.uniqueFiles.put(spdxDocs[i], uniqueAMap);
			}
			if (!diffMap.isEmpty()) {
				this.fileDifferences.put(spdxDocs[i], diffMap);
			}
		}
		if (!_isFilesEqualsNoCheck()) {
			this.differenceFound = true;
		}
	}
	
	/**
	 * Add all files found in the related elements (including descendant related elements)
	 * @param element
	 * @param files
	 * @throws InvalidSPDXAnalysisException 
	 */
	private void addAllRelatedFiles(SpdxElement element, Set files,
			Set visitedElements) throws InvalidSPDXAnalysisException {
		if (element == null || visitedElements.contains(element)) {
			return;
		}
		visitedElements.add(element);
		Relationship[] relationships = element.getRelationships();
		if (relationships != null) {
			for (int j = 0; j < relationships.length; j++) {
				if (relationships[j] != null && 
						relationships[j].getRelatedSpdxElement() instanceof SpdxFile &&
						!files.contains(relationships[j].getRelatedSpdxElement())) {
					files.add((SpdxFile)(relationships[j].getRelatedSpdxElement()));
				} else if (relationships[j] != null && 
						relationships[j].getRelatedSpdxElement() instanceof SpdxPackage) {
					SpdxFile[] pkgFiles = ((SpdxPackage)(relationships[j].getRelatedSpdxElement())).getFiles();
					if (pkgFiles != null) {
						for (int k = 0; k < pkgFiles.length; k++) {
							files.add(pkgFiles[k]);
						}
					}
				}
				// recursively add all of the related files to this relationships
				addAllRelatedFiles(relationships[j].getRelatedSpdxElement(), files, visitedElements);
			}
		}
	}
	
	/**
	 * Add all packages found in the related elements (including descendant related elements)
	 * @param element
	 * @param pkgs
	 * @throws InvalidSPDXAnalysisException 
	 */
	private void addAllRelatedPackages(SpdxElement element, 
			Set pkgs,
			Set visitedElements) throws InvalidSPDXAnalysisException {
		if (element == null || visitedElements.contains(element)) {
			return;
		}
		visitedElements.add(element);
		Relationship[] relationships = element.getRelationships();
		if (relationships != null) {
			for (int j = 0; j < relationships.length; j++) {
				if (relationships[j] != null && 
						relationships[j].getRelatedSpdxElement() instanceof SpdxPackage &&
						!pkgs.contains(relationships[j].getRelatedSpdxElement())) {
					pkgs.add((SpdxPackage)(relationships[j].getRelatedSpdxElement()));
				}
				// recursively add all of the related files to this relationships
				addAllRelatedPackages(relationships[j].getRelatedSpdxElement(), 
						pkgs, visitedElements);
			}
		}
	}
	
	/**
	 * Collect all of the packages present in the SPDX document including packages 
	 * embedded in other relationships within documents
	 * @param spdxDocument
	 * @return
	 * @throws InvalidSPDXAnalysisException 
	 */
	protected SpdxPackage[] collectAllPackages(SpdxDocument spdxDocument) throws InvalidSPDXAnalysisException {
		Set retval = Sets.newHashSet();
		SpdxItem[] items = spdxDocument.getDocumentDescribes();
		for (int i = 0; i < items.length; i++) {
			if (items[i] instanceof SpdxPackage) {
				retval.add((SpdxPackage)items[i]);
			}
			addAllRelatedPackages(items[i], retval, Sets.newHashSet());
		}	
		return retval.toArray(new SpdxPackage[retval.size()]);
	}

	/**
	 * Collect all of the files present in the SPDX document including files within documents
	 * and files embedded in packages
	 * @param spdxDocument
	 * @return
	 * @throws InvalidSPDXAnalysisException 
	 */
	protected SpdxFile[] collectAllFiles(SpdxDocument spdxDocument) throws InvalidSPDXAnalysisException {
		Set retval = Sets.newHashSet();
		SpdxItem[] items = spdxDocument.getDocumentDescribes();
		for (int i = 0; i < items.length; i++) {
			if (items[i] instanceof SpdxFile) {
				retval.add((SpdxFile)items[i]);			
			} else if (items[i] instanceof SpdxPackage) {
				SpdxFile[] pkgFiles = ((SpdxPackage)items[i]).getFiles();
				for (int j = 0; j < pkgFiles.length; j++) {
					retval.add(pkgFiles[j]);
				}
			}
			addAllRelatedFiles(items[i], retval, Sets.newHashSet());
		}	
		return retval.toArray(new SpdxFile[retval.size()]);
	}

	/**
	 * Returns an array of files differences between A and B where the names
	 * are the same, but one or more properties are different for that file
	 * @param filesA
	 * @param filesB
	 * @return
	 * @throws SpdxCompareException 
	 */
	static SpdxFileDifference[] findFileDifferences(SpdxDocument docA, SpdxDocument docB,
			SpdxFile[] filesA, SpdxFile[] filesB, 
			Map>> licenseIdXlationMap) throws SpdxCompareException {
		
		List alRetval = Lists.newArrayList();
		int aIndex = 0;
		int bIndex = 0;
		while (aIndex < filesA.length && bIndex < filesB.length) {
			int compare = filesA[aIndex].getName().compareTo(filesB[bIndex].getName());
			if (compare == 0) {
				SpdxFileComparer fileComparer = new SpdxFileComparer(licenseIdXlationMap);
				fileComparer.addDocumentFile(docA, filesA[aIndex]);
				fileComparer.addDocumentFile(docB, filesB[bIndex]);
				if (fileComparer.isDifferenceFound()) {
					alRetval.add(fileComparer.getFileDifference(docA, docB));
				}
				aIndex++;
				bIndex++;
			} else if (compare > 0) {
				// fileA is greater than fileB
				bIndex++;
			} else {
				// fileB is greater than fileA
				aIndex++;
			}
		}
		SpdxFileDifference[] retval = alRetval.toArray(new SpdxFileDifference[alRetval.size()]);
		return retval;
	}

	/**
	 * finds any packages in A that are not in B.  Packages are considered the
	 * same if they have the same package name and the same package version.
	 * NOTE: The arrays must be sorted by file name
	 * @param pkgsA
	 * @param pkgsB
	 * @return
	 */
	static SpdxPackage[] findUniquePackages(SpdxPackage[] pkgsA, SpdxPackage[] pkgsB) {
		int bIndex = 0;
		int aIndex = 0;
		List alRetval = Lists.newArrayList();
		while (aIndex < pkgsA.length) {
			if (bIndex >= pkgsB.length) {
				alRetval.add(pkgsA[aIndex]);
				aIndex++;
			} else {
				int compareVal = pkgsA[aIndex].compareTo(pkgsB[bIndex]);
				if (compareVal == 0) {
					// packages are equal
					aIndex++;
					bIndex++;
				} else if (compareVal > 0) {
					// pkgA is greater than pkgB
					bIndex++;
				} else {
					// pkgB is greater than pkgA
					alRetval.add(pkgsA[aIndex]);
					aIndex++;
				}
			}
		}
		SpdxPackage[] retval = alRetval.toArray(new SpdxPackage[alRetval.size()]);
		return retval;
	}
	/**
	 * finds any files in A that are not in B.  NOTE: The arrays must be sorted by file name
	 * @param filesA
	 * @param filesB
	 * @return
	 */
	static SpdxFile[] findUniqueFiles(SpdxFile[] filesA, SpdxFile[] filesB) {
		int bIndex = 0;
		int aIndex = 0;
		List alRetval = Lists.newArrayList();
		while (aIndex < filesA.length) {
			if (bIndex >= filesB.length) {
				alRetval.add(filesA[aIndex]);
				aIndex++;
			} else {
				int compareVal = filesA[aIndex].getName().compareTo(filesB[bIndex].getName());
				if (compareVal == 0) {
					// files are equal
					aIndex++;
					bIndex++;
				} else if (compareVal > 0) {
					// fileA is greater than fileB
					bIndex++;
				} else {
					// fileB is greater tha fileA
					alRetval.add(filesA[aIndex]);
					aIndex++;
				}
			}
		}
		SpdxFile[] retval = alRetval.toArray(new SpdxFile[alRetval.size()]);
		return retval;
	}

	/**
	 * @throws InvalidSPDXAnalysisException 
	 * 
	 */
	private void compareCreators() throws InvalidSPDXAnalysisException {
		this.creatorInformationEquals = true;
		this.licenseListVersionEquals = true;
		// this will be a N x N comparison of all creators to fill the
		// hashmap uniqueCreators
		for (int i = 0; i < spdxDocs.length; i++) {
			SPDXCreatorInformation creatorInfoA = spdxDocs[i].getCreationInfo();
			String[] creatorsA = creatorInfoA.getCreators();
			Map uniqueAMap = uniqueCreators.get(spdxDocs[i]);
			if (uniqueAMap == null) {
				uniqueAMap = Maps.newHashMap();
				// We will put this into the hashmap at the end of this method if it is not empty
			}
			for (int j = 0; j < spdxDocs.length; j++) {
				if (j == i) {
					continue;	// skip comparing to ourself
				}
				SPDXCreatorInformation creatorInfoB = spdxDocs[j].getCreationInfo();
				String[] creatorsB = creatorInfoB.getCreators();

				// find any creators in A that are not in B
				String[] uniqueA = findUniqueString(creatorsA, creatorsB);
				if (uniqueA != null && uniqueA.length > 0) {
					uniqueAMap.put(spdxDocs[j], uniqueA);					
				}
				// compare creator comments
				if (!stringsEqual(creatorInfoA.getComment(), creatorInfoB.getComment())) {
					this.creatorInformationEquals = false;
				}
				// compare creation dates
				if (!stringsEqual(creatorInfoA.getCreated(), creatorInfoB.getCreated())) {
					this.creatorInformationEquals = false;
				}
				// compare license list versions
				if (!stringsEqual(creatorInfoA.getLicenseListVersion(), creatorInfoB.getLicenseListVersion())) {
					this.creatorInformationEquals = false;
					this.licenseListVersionEquals = false;
				}
			}
			if (uniqueAMap.keySet().size() > 0) {
				this.uniqueCreators.put(spdxDocs[i], uniqueAMap);
				this.creatorInformationEquals = false;
			}
		}
		if (!this.creatorInformationEquals) {
			this.differenceFound = true;
		}	
	}

	/**
	 * Finds any strings which are in A but not in B
	 * @param stringsA
	 * @param stringsB
	 * @return
	 */
	private String[] findUniqueString(String[] stringsA, String[] stringsB) {
		if (stringsA == null) {
			return new String[0];
		}
		if (stringsB == null) {	
			return Arrays.copyOf(stringsA, stringsA.length);	
		}
		List al = Lists.newArrayList();
		for (int i = 0; i < stringsA.length; i++) {
			boolean found = false;
			for (int j = 0; j < stringsB.length; j++) {
				if (stringsA[i].trim().equals(stringsB[j].trim())) {
					found = true;
					break;
				}
			}
			if (!found) {
				al.add(stringsA[i]);
			}
		}
		return al.toArray(new String[al.size()]);
	}

	/**
	 * Compares the SPDX documents and sets the appropriate flags
	 * @throws SpdxCompareException 
	 */
	private void comparePackages() throws SpdxCompareException {
		if (this.spdxDocs == null || this.spdxDocs.length < 1) {
			return;
		}
		this.uniquePackages.clear();
		this.packageComparers.clear();
		// N x N comparison of all files
		for (int i = 0; i < spdxDocs.length; i++) {
			SpdxPackage[] pkgsA;
			try {
				pkgsA = collectAllPackages(spdxDocs[i]);
			} catch (InvalidSPDXAnalysisException e) {
				throw(new SpdxCompareException("Error collecting packages from SPDX document "+spdxDocs[i].getName(), e));
			}
			// note - the package arrays MUST be sorted for the comparator methods to work
			Arrays.sort(pkgsA);
			addPackageComparers(spdxDocs[i], pkgsA, this.extractedLicenseIdMap);
			Map uniqueAMap = this.uniquePackages.get(spdxDocs[i]);
			if (uniqueAMap == null) {
				uniqueAMap = Maps.newHashMap();
			}
			for (int j = 0; j < spdxDocs.length; j++) {
				if (j == i) {
					continue;
				}
				SpdxPackage[] pkgsB;
				try {
					pkgsB = collectAllPackages(spdxDocs[j]);
				} catch (InvalidSPDXAnalysisException e) {
					throw(new SpdxCompareException("Error collecting packages from SPDX document "+spdxDocs[i].getName(), e));
				}
				//Note that the files arrays must be sorted for the find methods to work
				Arrays.sort(pkgsB);
				SpdxPackage[] uniqueAB = findUniquePackages(pkgsA, pkgsB);
				if (uniqueAB != null && uniqueAB.length > 0) {
					uniqueAMap.put(spdxDocs[j], uniqueAB);
				}
			}
			if (!uniqueAMap.isEmpty()) {
				this.uniquePackages.put(spdxDocs[i], uniqueAMap);
			}
		}
		if (!_isPackagesEqualsNoCheck()) {
			this.differenceFound = true;
		}		
	}

	/**
	 * add all the document packages to the multi-comparer
	 * @param spdxDocument
	 * @param pkgs
	 * @param extractedLicenseIdMap 
	 * @throws SpdxCompareException 
	 */
	private void addPackageComparers(SpdxDocument spdxDocument,
			SpdxPackage[] pkgs, Map>> extractedLicenseIdMap) throws SpdxCompareException {
		for (int i = 0; i < pkgs.length; i++) {
			SpdxPackageComparer mpc = this.packageComparers.get(pkgs[i].getName());
			if (mpc == null) {
				mpc = new SpdxPackageComparer(extractedLicenseIdMap);
				this.packageComparers.put(pkgs[i].getName(), mpc);
			}
			mpc.addDocumentPackage(spdxDocument, pkgs[i]);
		}
	}

	/**
	 * Compares two licenses from two different SPDX documents taking into account
	 * the extracted license infos who's ID's may be different between the two documents
	 * Note: The ExtracedLicenseIDMap must be initialized before this method is invoked
	 * @param doc1 Index of the SPDX document for license1
	 * @param license1
	 * @param doc2 Index of the SPDX document for license2
	 * @param license2
	 * @return true if the licenses are equivalent
	 * @throws SpdxCompareException 
	 */
	public boolean compareLicense(int doc1,
			AnyLicenseInfo license1, int doc2,
			AnyLicenseInfo license2) throws SpdxCompareException {
		this.checkDocsIndex(doc1);
		this.checkDocsIndex(doc2);
		Map> hm = this.extractedLicenseIdMap.get(this.spdxDocs[doc1]);
		if (hm == null) {
			throw(new SpdxCompareException("Compare License Error - Extracted license id map has not been initialized."));
		}
		Map xlationMap = hm.get(this.spdxDocs[doc2]);
		if (xlationMap == null) {
			throw(new SpdxCompareException("Compare License Exception - Extracted license id map has not been initialized."));
		}
		return LicenseCompareHelper.isLicenseEqual(license1, license2, xlationMap);
	}

	/**
	 * @param verificationCode
	 * @param verificationCode2
	 * @return
	 */
	static boolean compareVerificationCodes(
			SpdxPackageVerificationCode verificationCode,
			SpdxPackageVerificationCode verificationCode2) {
		if (verificationCode == null) {
			return verificationCode2 == null;
		}
		if (verificationCode2 == null) {
			return false;
		}
		if (!stringsEqual(verificationCode.getValue(), verificationCode2.getValue())) {
			return false;
		}
		if (!stringArraysEqual(verificationCode.getExcludedFileNames(), 
				verificationCode2.getExcludedFileNames())) {
			return false;
		}
		return true;		
	}

	/**
	 * Compare the document level fields and sets the difference found depending on any differences
	 * @throws SpdxCompareException 
	 */
	private void compareDocumentFields() throws SpdxCompareException {
		compareDataLicense();
		compareDocumentComments();
		compareSpdxVerions();
		compareDocumentContents();
		if (!this.dataLicenseEqual || !this.spdxVersionsEqual || !this.documentCommentsEqual) {
			this.differenceFound = true;
		}
	}
	
	private void compareDocumentContents() throws SpdxCompareException {
		documentContentsEquals = true;
		try {
			for (int i = 0; i < spdxDocs.length; i++) {
				SpdxItem[] itemsA = spdxDocs[i].getDocumentDescribes();
				for (int j = i; j < spdxDocs.length; j++) {
					SpdxItem[] itemsB = spdxDocs[j].getDocumentDescribes();
					if (!spdxDocs[i].arraysEquivalent(itemsA, itemsB)) {
						this.documentContentsEquals = false;
						this.differenceFound = true;
						return;
					}
				}
			}
		} catch(InvalidSPDXAnalysisException ex) {
			throw(new SpdxCompareException("Error getting SPDX document items: "+ex.getMessage()));
		}
	}

	/**
	 * @throws SpdxCompareException 
	 * 
	 */
	private void compareSpdxVerions() throws SpdxCompareException {
		String docVer1;
		docVer1 = spdxDocs[0].getSpecVersion();
		this.spdxVersionsEqual = true;
		for (int i = 1; i < spdxDocs.length; i++) {
			if (!spdxDocs[i].getSpecVersion().equals(docVer1)) {
				this.spdxVersionsEqual = false;
				break;
			}
		}
	}

	/**
	 * @throws SpdxCompareException 
	 * 
	 */
	private void compareDocumentComments() throws SpdxCompareException {
		String comment1 = this.spdxDocs[0].getComment();
		this.documentCommentsEqual = true;
		for (int i = 1; i < spdxDocs.length; i++) {
			String comment2 = this.spdxDocs[i].getComment();
			if (!stringsEqual(comment1, comment2)) {
				this.documentCommentsEqual = false;
				break;
			}
		}
	}

	/**
	 * @throws SpdxCompareException 
	 * 
	 */
	private void compareDataLicense() throws SpdxCompareException {
		try {
			AnyLicenseInfo lic1 = this.spdxDocs[0].getDataLicense();
			this.dataLicenseEqual = true;
			for (int i = 1; i < spdxDocs.length; i++) {
				if (!lic1.equals(spdxDocs[i].getDataLicense())) {
					this.dataLicenseEqual = false;
					break;
				}
			}
		} catch (InvalidSPDXAnalysisException e) {
			throw(new SpdxCompareException("SPDX analysis error during compare data license: "+e.getMessage(),e));
		}
	}

	/**
	 * Compares the extracted license infos in all documents and builds the 
	 * maps for translating IDs as well as capturing any differences between the
	 * extracted licensing information
	 * @throws InvalidSPDXAnalysisException 
	 * @throws SpdxCompareException 
	 */
	private void compareExtractedLicenseInfos() throws InvalidSPDXAnalysisException, SpdxCompareException {
		for (int i = 0; i < spdxDocs.length; i++) {
			ExtractedLicenseInfo[] extractedLicensesA = spdxDocs[i].getExtractedLicenseInfos();
			Map uniqueMap = Maps.newHashMap();
				Map differenceMap = Maps.newHashMap();
				Map> licenseIdMap = Maps.newHashMap();

			for (int j = 0; j < spdxDocs.length; j++) {
				if (i == j) {
					continue;	// no need to compare to ourself;
				}
				Map idMap = Maps.newHashMap();
				List alDifferences = Lists.newArrayList();
				ExtractedLicenseInfo[] extractedLicensesB = spdxDocs[j].getExtractedLicenseInfos();
				List uniqueLicenses = Lists.newArrayList();
				compareLicenses(extractedLicensesA, extractedLicensesB,
						idMap, alDifferences, uniqueLicenses);
				// unique
				if (uniqueLicenses.size() > 0) {
					uniqueMap.put(spdxDocs[j], uniqueLicenses.toArray(
							new ExtractedLicenseInfo[uniqueLicenses.size()]));
				}
				// differences
				if (alDifferences.size() > 0) {
					differenceMap.put(spdxDocs[j], alDifferences.toArray(
							new SpdxLicenseDifference[alDifferences.size()]));
				}
				// map
				licenseIdMap.put(spdxDocs[j], idMap);
			}
			if (uniqueMap.keySet().size() > 0) {
				this.uniqueExtractedLicenses.put(spdxDocs[i], uniqueMap);
			}
			if (differenceMap.keySet().size() > 0) {
				this.licenseDifferences.put(spdxDocs[i], differenceMap);
			}
			this.extractedLicenseIdMap.put(spdxDocs[i], licenseIdMap);
		}
		if (!_isExtractedLicensingInfoEqualsNoCheck()) {
			this.differenceFound = true;
		}
	}

	/**
	 * Compares two arrays of non standard licenses
	 * @param extractedLicensesA
	 * @param extractedLicensesB
	 * @param idMap Map of license IDs for licenses considered equal
	 * @param alDifferences Array list of license differences found where the license text is equivalent but other properties are different
	 * @param uniqueLicenses ArrayList if licenses found in the A but not found in B
	 */
	private void compareLicenses(ExtractedLicenseInfo[] extractedLicensesA,
			ExtractedLicenseInfo[] extractedLicensesB,
			Map idMap,
			List alDifferences,
			List uniqueLicenses) {
		idMap.clear();
		alDifferences.clear();
		uniqueLicenses.clear();
		for (int k = 0; k < extractedLicensesA.length; k++) {
			boolean foundMatch = false;
			boolean foundTextMatch = false;
			for (int q = 0; q < extractedLicensesB.length; q++) {
				if (LicenseCompareHelper.isLicenseTextEquivalent(extractedLicensesA[k].getExtractedText(), 
						extractedLicensesB[q].getExtractedText())) {
					foundTextMatch = true;
					if (!foundMatch) {
						idMap.put(extractedLicensesA[k].getLicenseId(), extractedLicensesB[q].getLicenseId());
						// always add to the map any matching licenses.  If more than one, add
						// the license matches where the entire license match.  This condition checks
						// to make sure we are not over-writing an exact match
					}
					if (nonTextLicenseFieldsEqual(extractedLicensesA[k], extractedLicensesB[q])) {
						foundMatch = true;
					} else {
						alDifferences.add(new SpdxLicenseDifference(extractedLicensesA[k], extractedLicensesB[q]));
					}
				}
			}
			if (!foundTextMatch) {	// we treat the licenses as equivalent if the text matches even if other fields do not match
				uniqueLicenses.add(extractedLicensesA[k]);
			}
		}
	}

	/**
	 * Compares the non-license text and non-id fields and returns true
	 * if all relevant fields are equal
	 * @param spdxNonStandardLicenseA
	 * @param spdxNonStandardLicenseB
	 * @return
	 */
	private boolean nonTextLicenseFieldsEqual(
			ExtractedLicenseInfo spdxNonStandardLicenseA,
			ExtractedLicenseInfo spdxNonStandardLicenseB) {
		
		// license name
		if (!stringsEqual(spdxNonStandardLicenseA.getName(),
				spdxNonStandardLicenseB.getName())) {
			return false;
		}

		// comment;
		if (!stringsEqual(spdxNonStandardLicenseA.getComment(),
					spdxNonStandardLicenseB.getComment())) {
			return false;
		}
		// Source URL's
		if (!stringArraysEqual(spdxNonStandardLicenseA.getSeeAlso(), spdxNonStandardLicenseB.getSeeAlso())) {
			return false;
		}
		// if we made it here, everything is equal
		return true;
	}

	/**
	 * Compares 2 arrays and returns true if the contents are equal
	 * ignoring order and trimming strings.  Nulls are also considered as equal to other nulls.
	 * @param stringsA
	 * @param stringsB
	 * @return
	 */
	static boolean stringArraysEqual(String[] stringsA, String[] stringsB) {
		
		if (stringsA == null) {
			if (stringsB != null) {
				return false;
			}
		} else {
			if (stringsB == null) {
				return false;
			}
			if (stringsA.length != stringsB.length) {
				return false;
			}
			Set foundIndexes = Sets.newHashSet();
			for (int i = 0; i < stringsA.length; i++) {
				boolean found = false;
				for (int j = 0; j < stringsB.length; j++) {
					if (!foundIndexes.contains(j) &&
							stringsEqual(stringsA[i], stringsB[j])) {
						found = true;
						foundIndexes.add(j);
						break;
					}
				}
				if (!found) {
					return false;
				}
			}
		}
		return true;
	}
	
	/**
	 * returns true if the two objects are equal considering nulls
	 * @param o1
	 * @param o2
	 * @return
	 */
	public static boolean objectsEqual(Object o1, Object o2) {
		if (o1 == null) {
			return o2 == null;
		}
		return o1.equals(o2);
	}
	
	public static boolean elementsEquivalent(RdfModelObject elementA, RdfModelObject elementB) {
		if (elementA == null) {
			return elementB == null;
		}
		return elementA.equivalent(elementB);
	}

	/**
	 * Compare two object arrays
	 * @param a1
	 * @param a2
	 * @return
	 */
	public static boolean arraysEqual(Object[] a1, Object[] a2) {
		if (a1 == null) {
			if (a2 != null) {
				return false;
			}
		} else {
			if (a2 == null) {
				return false;
			}
			if (a1.length != a2.length) {
				return false;
			}
			for (int i = 0; i < a1.length; i++) {
				boolean found = false;
				for (int j = 0; j < a2.length; j++) {
					if (objectsEqual(a1[i], a2[j])) {
						found = true;
						break;
					}
				}
				if (!found) {
					return false;
				}
			}
		}
		return true;
	}
	/**
	 * Compares two strings returning true if they are equal
	 * considering null values and trimming the strings. and normalizing
	 * linefeeds.  Empty strings are treated as the same as null values.
	 * @param stringA
	 * @param stringB
	 * @return
	 */
	public static boolean stringsEqual(String stringA, String stringB) {
		String compA;
		String compB;
		if (stringA == null) {
			compA = "";
		} else {
			compA = stringA.replace("\r\n", "\n").trim();
		}
		if (stringB == null) {
			compB = "";
		} else {
			compB = stringB.replace("\r\n", "\n").trim();
		}
		return (compA.equals(compB));
	}
	
	/**
	 * Compares two strings including trimming the string and taking into account
	 * they may be null.  Null is considered a smaller value
	 * @param stringA
	 * @param stringB
	 * @return
	 */
	public static int compareStrings(String stringA, String stringB) {
		if (stringA == null) {
			if (stringB == null) {
				return 0;
			} else {
				return -1;
			}
		}
		if (stringB == null) {
			return 1;
		}
		return (stringA.trim().compareTo(stringB.trim()));
	}

	/**
	 * Compares the reviewers for all documents and creates the differences hasmaps related
	 * to the reviewers
	 * @throws InvalidSPDXAnalysisException 
	 * @throws SpdxCompareException 
	 */
	private void compareReviewers() throws InvalidSPDXAnalysisException, SpdxCompareException {
		// this will be a N x N comparison of all reviewer data to fill in the
		// hashmaps uniqueReviews
		for (int i = 0; i < spdxDocs.length; i++) {
			@SuppressWarnings("deprecation")
			SPDXReview[] reviewA = spdxDocs[i].getReviewers();
			Map uniqueAMap = uniqueReviews.get(spdxDocs[i]);
			if (uniqueAMap == null) {
				uniqueAMap = Maps.newHashMap();
				// We will put this into the hashmap at the end of this method if it is not empty
			}
			Map diffMap = this.reviewerDifferences.get(this.spdxDocs[i]);
			if (diffMap == null) {
				diffMap = Maps.newHashMap();
				// We will put this into the hashmap at the end of this method if it is not empty
			}
			for (int j = 0; j < spdxDocs.length; j++) {
				if (j == i) {
					continue;	// skip comparing to ourself
				}
				@SuppressWarnings("deprecation")
				SPDXReview[] reviewB = spdxDocs[j].getReviewers();
				// find any reviewers in A that are not in B
				SPDXReview[] uniqueA = findUniqueReviewers(reviewA, reviewB);
				if (uniqueA != null && uniqueA.length > 0) {
					uniqueAMap.put(spdxDocs[j], uniqueA);					
				}
				//Find any reviewers that are the same reviewer but have different dates or comments
				SPDXReviewDifference[] reviewerDifferences = findReviewerDifferences(reviewA, reviewB);
				if (reviewerDifferences != null && reviewerDifferences.length > 0) {
					diffMap.put(this.spdxDocs[j], reviewerDifferences);
				}
			}
			if (uniqueAMap.keySet().size() > 0) {
				this.uniqueReviews.put(spdxDocs[i], uniqueAMap);
			}
			if (diffMap.keySet().size() > 0) {
				this.reviewerDifferences.put(this.spdxDocs[i], diffMap);
			}
		}
		if (!this._isReviewersEqualNoCheck()) {
			this.differenceFound = true;
		}
	}

	/**
	 * Compares two arrays of SPDXReview and returns any differences found
	 * A difference is an SPDXReview with the same reviewer but a different comment and date
	 * @param reviewA
	 * @param reviewB
	 * @return
	 */
	private SPDXReviewDifference[] findReviewerDifferences(
			SPDXReview[] reviewA, SPDXReview[] reviewB) {
		//Note that we need to take into account the possibility of two SPDXReviews in the
		//same array with the same reviewer name
		List retval = Lists.newArrayList();
		for (int i = 0; i < reviewA.length; i++) {
			boolean reviewDifferent = false;
			int differentReviewerIndex = -1;
			for (int j = 0; j < reviewB.length; j++) {
				if (reviewA[i].getReviewer().trim().equals(reviewB[j].getReviewer().trim())) {
					// reviewer name is the same
					boolean commentsEqual = reviewA[i].getComment().trim().equals(reviewB[j].getComment().trim());
					boolean datesEqual = reviewA[i].getReviewDate().equals(reviewB[j].getReviewDate());
					if (commentsEqual && datesEqual) {
						reviewDifferent = false;	// note that we may have a situation where a previous 
						// entry was found with the same reviewer name and a different comment/date
						// in that situation, we should report no difference since a matching entry was found
						break;
					} else {
						reviewDifferent = true;
						differentReviewerIndex = j;
					}
				} 
			}
			if (reviewDifferent) {
				retval.add(new SPDXReviewDifference(reviewA[i], reviewB[differentReviewerIndex]));
			}
		}
		return retval.toArray(new SPDXReviewDifference[retval.size()]);
	}

	/**
	 * Finds any reviewer names that are contained in reviewA but not in reviewB
	 * @param reviewA
	 * @param reviewB
	 * @return
	 */
	private SPDXReview[] findUniqueReviewers(SPDXReview[] reviewA,
			SPDXReview[] reviewB) {
		List retval = Lists.newArrayList();
		for (int i = 0; i < reviewA.length; i++) {
			boolean found = false;
			for (int j = 0; j < reviewB.length; j++) {
				if (reviewA[i].getReviewer().trim().equals(reviewB[j].getReviewer().trim())) {
					found = true;
					break;
				}
			}
			if (!found) {
				retval.add(reviewA[i]);
			}
		}
		return retval.toArray(new SPDXReview[retval.size()]);
	}

	/**
	 * 
	 */
	private void clearCompareResults() {
		this.differenceFound = false;
		this.reviewerDifferences.clear();
		this.uniqueReviews.clear();
		this.licenseDifferences.clear();
		this.uniqueExtractedLicenses.clear();
		this.extractedLicenseIdMap.clear();
		this.uniqueCreators.clear();
	}

	/**
	 * @return
	 */
	public boolean isDifferenceFound() {
		return this.differenceFound;
	}

	/**
	 * @return
	 * @throws SpdxCompareException 
	 */
	public boolean isSpdxVersionEqual() throws SpdxCompareException {
		checkInProgress();
		checkDocsField();
		return this.spdxVersionsEqual;
	}

	/**
	 * checks to make sure there is not a compare in progress
	 * @throws SpdxCompareException 
	 * 
	 */
	private void checkInProgress() throws SpdxCompareException {
		if (compareInProgress) {
			throw(new SpdxCompareException("Compare in progress - can not obtain compare results until compare has completed"));
		}
	}

	/**
	 * Validates that the spdx dcouments field has been initialized
	 * @throws SpdxCompareException 
	 */
	private void checkDocsField() throws SpdxCompareException {
		if (this.spdxDocs == null) {
			throw(new SpdxCompareException("No compare has been performed"));
		}
		if (this.spdxDocs.length < 2) {
			throw(new SpdxCompareException("Insufficient documents compared - must provide at least 2 SPDX documents"));
		}
	}
	
	private void checkDocsIndex(int index) throws SpdxCompareException {
		if (this.spdxDocs == null) {
			throw(new SpdxCompareException("No compare has been performed"));
		}
		if (index < 0) {
			throw(new SpdxCompareException("Invalid index for SPDX document compare - must be greater than or equal to zero"));
		}
		if (index >= spdxDocs.length) {
			throw(new SpdxCompareException("Invalid index for SPDX document compare - SPDX document index "+String.valueOf(index)+" does not exist."));
		}
	}

	/**
	 * @param docIndex Reference to which document number - 0 is the first document parameter in compare
	 * @return
	 * @throws SpdxCompareException 
	 */
	public SpdxDocument getSpdxDoc(int docIndex) throws SpdxCompareException {
		this.checkDocsField();
		if (this.spdxDocs ==  null) {
			return null;
		}
		if (docIndex < 0) {
			return null;
		}
		if (docIndex > this.spdxDocs.length) {
			return null;
		}
		return this.spdxDocs[docIndex];
	}

	/**
	 * @return
	 * @throws SpdxCompareException 
	 */
	public boolean isDataLicenseEqual() throws SpdxCompareException {
		checkInProgress();
		checkDocsField();
		return this.dataLicenseEqual;
	}

	/**
	 * @return
	 * @throws SpdxCompareException 
	 */
	public boolean isDocumentCommentsEqual() throws SpdxCompareException {
		checkInProgress();
		checkDocsField();
		return this.documentCommentsEqual;
	}

	/**
	 * @return
	 * @throws SpdxCompareException 
	 */
	public boolean isReviewersEqual() throws SpdxCompareException {
		checkInProgress();
		checkDocsField();
		return _isReviewersEqualNoCheck();
	}
	
	
	/**
	 * @return
	 */
	private boolean _isReviewersEqualNoCheck() {
		// check for unique reviewers
		Iterator>> uniqueIter =
			this.uniqueReviews.entrySet().iterator();
		while (uniqueIter.hasNext()) {
			Entry > entry = uniqueIter.next();
			Iterator> entryIter = entry.getValue().entrySet().iterator();
			while (entryIter.hasNext()) {
				SPDXReview[] val = entryIter.next().getValue();
				if (val != null && val.length > 0) {
					return false;
				}
			}
		}
		// check differences
		Iterator>> diffIter = this.reviewerDifferences.entrySet().iterator();
		while (diffIter.hasNext()) {
			Iterator> entryIter = diffIter.next().getValue().entrySet().iterator();
			while(entryIter.hasNext()) {
				SPDXReviewDifference[] reviewDifferences = entryIter.next().getValue();
				if (reviewDifferences != null && reviewDifferences.length > 0) {
					return false;
				}
			}
		}
		// if we got to here - they are equal
		return true;
	}
	
	private boolean _isExternalDcoumentRefsEqualsNoCheck() {
		Iterator>> iter = this.uniqueExternalDocumentRefs.entrySet().iterator();
		while (iter.hasNext()) {
			Iterator docIterator = iter.next().getValue().values().iterator();
			while (docIterator.hasNext()) {
				if (docIterator.next().length > 0) {
					return false;
				}
			}
		}
		return true;
	}


	public boolean isExternalDcoumentRefsEquals() throws SpdxCompareException {
		checkInProgress();
		checkDocsField();
		return _isExternalDcoumentRefsEqualsNoCheck();
	}

	
	public boolean isExtractedLicensingInfosEqual() throws SpdxCompareException {
		checkInProgress();
		checkDocsField();
		return _isExtractedLicensingInfoEqualsNoCheck();
	}

	/**
	 * @return
	 */
	private boolean _isExtractedLicensingInfoEqualsNoCheck() {
		// check for unique extraced license infos
		Iterator>> uniqueIter = 
			this.uniqueExtractedLicenses.entrySet().iterator();
		while (uniqueIter.hasNext()) {
			Entry> entry = uniqueIter.next();
			Iterator> entryIter = entry.getValue().entrySet().iterator();
			while(entryIter.hasNext()) {
				ExtractedLicenseInfo[] licenses = entryIter.next().getValue();
				if (licenses != null && licenses.length > 0) {
					return false;
				}
			}
		}
		// check differences
		Iterator>> diffIterator = this.licenseDifferences.entrySet().iterator();
		while (diffIterator.hasNext()) {
			Iterator> entryIter = diffIterator.next().getValue().entrySet().iterator();
			while (entryIter.hasNext()) {
				SpdxLicenseDifference[] differences = entryIter.next().getValue();
				if (differences != null && differences.length > 0) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Get all unique reviewers in SPDX document at index 1 relative to reviewers
	 * in SPDX Document at index 2
	 * @param docindex1
	 * @param docindex2
	 * @return
	 * @throws SpdxCompareException 
	 */
	public SPDXReview[] getUniqueReviewers(int docindex1, int docindex2) throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		checkDocsIndex(docindex1);
		checkDocsIndex(docindex2);
		Map uniques = this.uniqueReviews.get(spdxDocs[docindex1]);
		if (uniques != null) {
			SPDXReview[] retval = uniques.get(spdxDocs[docindex2]);
			if (retval != null) {
				return retval;
			} else {
				return new SPDXReview[0];
			}
		} else {
			return new SPDXReview[0];
		}
	}

	/**
	 * Get all reviewer differences between two documents.  A reviewer difference is
	 * where the reviewer name is the same but the reviewer date and/or the reviewer comment
	 * is different
	 * @param docindex1
	 * @param docindex2
	 * @return
	 * @throws SpdxCompareException 
	 */
	public SPDXReviewDifference[] getReviewerDifferences(int docindex1, int docindex2) throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		checkDocsIndex(docindex1);
		checkDocsIndex(docindex2);
		Map doc1Differences =
			this.reviewerDifferences.get(spdxDocs[docindex1]);
		if (doc1Differences == null) {
			return new SPDXReviewDifference[0];
		}
		SPDXReviewDifference[] retval = doc1Differences.get(spdxDocs[docindex2]);
		if (retval == null) {
			return new SPDXReviewDifference[0];
		} 
		return retval;
	}

	/**
	 * Retrieves any unique extracted licenses fromt the first SPDX document index
	 * relative to the second - unique is determined by the license text matching
	 * @param docIndexA
	 * @param docIndexB
	 * @return
	 * @throws SpdxCompareException 
	 */
	public ExtractedLicenseInfo[] getUniqueExtractedLicenses(int docIndexA, int docIndexB) throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		checkDocsIndex(docIndexA);
		checkDocsIndex(docIndexB);
		Map uniques = this.uniqueExtractedLicenses.get(spdxDocs[docIndexA]);
		if (uniques != null) {
			ExtractedLicenseInfo[] retval = uniques.get(spdxDocs[docIndexB]);
			if (retval != null) {
				return retval;
			} else {
				return new ExtractedLicenseInfo[0];
			}
		} else {
			return new ExtractedLicenseInfo[0];
		}
	}

	/**
	 * Retrieves any licenses which where the text matches in both documents but
	 * other fields are different
	 * @param docIndexA
	 * @param docIndexB
	 * @return
	 * @throws SpdxCompareException 
	 */
	public SpdxLicenseDifference[] getExtractedLicenseDifferences(int docIndexA, int docIndexB) throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		checkDocsIndex(docIndexA);
		checkDocsIndex(docIndexB);
		Map differences = this.licenseDifferences.get(spdxDocs[docIndexA]);
		if (differences != null) {
			SpdxLicenseDifference[] retval = differences.get(spdxDocs[docIndexB]);
			if (retval != null) {
				return retval;
			} else {
				return new SpdxLicenseDifference[0];
			}
		} else {
			return new SpdxLicenseDifference[0];
		}
	}

	/**
	 * @return
	 * @throws SpdxCompareException 
	 */
	public boolean isCreatorInformationEqual() throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		return this.creatorInformationEquals;
	}

	/**
	 * Returns any creators which are in the SPDX document 1 which are not in document 2
	 * @param doc1index
	 * @param doc2index
	 * @return
	 * @throws SpdxCompareException 
	 */
	public String[] getUniqueCreators(int doc1index, int doc2index) throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		Map uniques = this.uniqueCreators.get(this.getSpdxDoc(doc1index));
		if (uniques == null) {
			return new String[0];
		}
		String[] retval = uniques.get(this.getSpdxDoc(doc2index));
		if (retval == null) {
			return new String[0];
		} else {
			return retval;
		}
	}

	/**
	 * @return
	 * @throws SpdxCompareException 
	 */
	public boolean isfilesEquals() throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		return this._isFilesEqualsNoCheck();
	}
	
	/**
	 * @return
	 * @throws SpdxCompareException 
	 */
	public boolean isPackagesEquals() throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		return this._isPackagesEqualsNoCheck();
	}

	/**
	 * @return
	 * @throws SpdxCompareException
	 */
	public boolean isDocumentAnnotationsEquals() throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		return _isDocumentAnnotationsEqualsNoCheck();
	}
	/**
	 * @return
	 */
	private boolean _isDocumentAnnotationsEqualsNoCheck() {
		Iterator>> iter = this.uniqueDocumentAnnotations.entrySet().iterator();
		while (iter.hasNext()) {
			Iterator docIterator = iter.next().getValue().values().iterator();
			while (docIterator.hasNext()) {
				if (docIterator.next().length > 0) {
					return false;
				}
			}
		}
		return true;
	}
	
	/**
	 * @return
	 * @throws SpdxCompareException
	 */
	public boolean isDocumentRelationshipsEquals() throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		return _isDocumentRelationshipsEqualsNoCheck();
	}
	/**
	 * @return
	 */
	private boolean _isDocumentRelationshipsEqualsNoCheck() {
		Iterator>> iter = this.uniqueDocumentRelationships.entrySet().iterator();
		while (iter.hasNext()) {
			Iterator docIterator = iter.next().getValue().values().iterator();
			while (docIterator.hasNext()) {
				if (docIterator.next().length > 0) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * @return
	 */
	private boolean _isFilesEqualsNoCheck() {
		if (!this.uniqueFiles.isEmpty()) {
			return false;
		}
		if (!this.fileDifferences.isEmpty()) {
			return false;
		}
		return true;
	}

	/**
	 * @return
	 * @throws SpdxCompareException 
	 */
	private boolean _isPackagesEqualsNoCheck() throws SpdxCompareException {
		Iterator>> iter = this.uniquePackages.entrySet().iterator();
		while (iter.hasNext()) {
			Iterator docIterator = iter.next().getValue().values().iterator();
			while (docIterator.hasNext()) {
				if (docIterator.next().length > 0) {
					return false;
				}
			}
		}
		Iterator diffIter = this.packageComparers.values().iterator();
		while (diffIter.hasNext()) {
			if (diffIter.next().isDifferenceFound()) {
				return false;
			}
		}
		return true;
	}
	/**
	 * Return any files which are in spdx document index 1 but not in spdx document index 2
	 * @param docindex1
	 * @param docindex2
	 * @return
	 * @throws SpdxCompareException 
	 */
	public SpdxFile[] getUniqueFiles(int docindex1, int docindex2) throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		this.checkDocsIndex(docindex1);
		this.checkDocsIndex(docindex2);
		Map uniqueMap = this.uniqueFiles.get(this.spdxDocs[docindex1]);
		if (uniqueMap == null) {
			return new SpdxFile[0];
		}
		SpdxFile[] retval = uniqueMap.get(this.spdxDocs[docindex2]);
		if (retval == null) {
			return new SpdxFile[0];
		}
		return retval;
	}

	/**
	 * Returns any file differences found between the first and second SPDX documents
	 * as specified by the document index
	 * @param docindex1
	 * @param docindex2
	 * @return
	 * @throws SpdxCompareException 
	 */
	public SpdxFileDifference[] getFileDifferences(int docindex1, int docindex2) throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		this.checkDocsIndex(docindex1);
		this.checkDocsIndex(docindex2);
		Map uniqueMap = this.fileDifferences.get(this.spdxDocs[docindex1]);
		if (uniqueMap == null) {
			return new SpdxFileDifference[0];
		}
		SpdxFileDifference[] retval = uniqueMap.get(this.spdxDocs[docindex2]);
		if (retval == null) {
			return new SpdxFileDifference[0];
		}
		return retval;
	}
	
	/**
	 * Return any files which are in spdx document index 1 but not in spdx document index 2
	 * @param docindex1
	 * @param docindex2
	 * @return
	 * @throws SpdxCompareException 
	 */
	public SpdxPackage[] getUniquePackages(int docindex1, int docindex2) throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		this.checkDocsIndex(docindex1);
		this.checkDocsIndex(docindex2);
		Map uniqueMap = this.uniquePackages.get(this.spdxDocs[docindex1]);
		if (uniqueMap == null) {
			return new SpdxPackage[0];
		}
		SpdxPackage[] retval = uniqueMap.get(this.spdxDocs[docindex2]);
		if (retval == null) {
			return new SpdxPackage[0];
		}
		return retval;
	}
	
	/**
	 * Return any external document references which are in spdx document index 1 but not in spdx document index 2
	 * @param docindex1
	 * @param docindex2
	 * @return
	 * @throws SpdxCompareException
	 */
	public ExternalDocumentRef[] getUniqueExternalDocumentRefs(int docindex1, int docindex2) throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		this.checkDocsIndex(docindex1);
		this.checkDocsIndex(docindex2);
		Map uniqueMap = this.uniqueExternalDocumentRefs.get(this.spdxDocs[docindex1]);
		if (uniqueMap == null) {
			return new ExternalDocumentRef[0];
		}
		ExternalDocumentRef[] retval = uniqueMap.get(this.spdxDocs[docindex2]);
		if (retval == null) {
			return new ExternalDocumentRef[0];
		}
		return retval;
	}

	/**
	 * Return any document annotations which are in spdx document index 1 but not in spdx document index 2
	 * @param docindex1
	 * @param docindex2
	 * @return
	 * @throws SpdxCompareException
	 */
	public Annotation[] getUniqueDocumentAnnotations(int docindex1, int docindex2) throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		this.checkDocsIndex(docindex1);
		this.checkDocsIndex(docindex2);
		Map uniqueMap = this.uniqueDocumentAnnotations.get(this.spdxDocs[docindex1]);
		if (uniqueMap == null) {
			return new Annotation[0];
		}
		Annotation[] retval = uniqueMap.get(this.spdxDocs[docindex2]);
		if (retval == null) {
			return new Annotation[0];
		}
		return retval;
	}
	
	/**
	 * Return any document annotations which are in spdx document index 1 but not in spdx document index 2
	 * @param docindex1
	 * @param docindex2
	 * @return
	 * @throws SpdxCompareException
	 */
	public Relationship[] getUniqueDocumentRelationship(int docindex1, int docindex2) throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		this.checkDocsIndex(docindex1);
		this.checkDocsIndex(docindex2);
		Map uniqueMap = this.uniqueDocumentRelationships.get(this.spdxDocs[docindex1]);
		if (uniqueMap == null) {
			return new Relationship[0];
		}
		Relationship[] retval = uniqueMap.get(this.spdxDocs[docindex2]);
		if (retval == null) {
			return new Relationship[0];
		}
		return retval;
	}
	
	/**
	 * @return Package comparers where there is at least one difference
	 * @throws SpdxCompareException 
	 */
	public SpdxPackageComparer[] getPackageDifferences() throws SpdxCompareException {
		Collection comparers = this.packageComparers.values();
		Iterator iter = comparers.iterator();
		int count = 0;
		while (iter.hasNext()) {
			if (iter.next().isDifferenceFound()) {
				count++;
			}
		}
		SpdxPackageComparer[] retval = new SpdxPackageComparer[count];		
		iter = comparers.iterator();
		int i = 0;
		while (iter.hasNext()) {
			SpdxPackageComparer comparer = iter.next();
			if (comparer.isDifferenceFound()) {
				retval[i++] = comparer;
			}
		}
		return retval;
	}
	
	/**
	 * @return all package comparers
	 */
	public SpdxPackageComparer[] getPackageComparers() {
		return this.packageComparers.values().toArray(
				new SpdxPackageComparer[this.packageComparers.values().size()]);
	}

	/**
	 * @return
	 */
	public int getNumSpdxDocs() {
		return this.spdxDocs.length;
	}

	/**
	 * @return
	 * @throws SpdxCompareException 
	 */
	public boolean isLicenseListVersionEqual() throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		return this.licenseListVersionEquals;
	}

	/**
	 * Find any SPDX checksums which are in elementsA but not in elementsB
	 * @param checksumsA
	 * @param checksumsB
	 * @return
	 */
	public static Checksum[] findUniqueChecksums(Checksum[] checksumsA,
			Checksum[] checksumsB) {
		List retval = Lists.newArrayList();
		if (checksumsA != null) {
			for (int i = 0; i < checksumsA.length; i++) {
				if (checksumsA[i] == null) {
					continue;
				}
				boolean found = false;
				if (checksumsB != null) {
					for (int j = 0; j < checksumsB.length; j++) {
						if (checksumsA[i].equivalent(checksumsB[j])) {
							found = true;
							break;
						}
					}
				}
				if (!found) {
					retval.add(checksumsA[i]);
				}
			}
		}
		return retval.toArray(new Checksum[retval.size()]);
	}
	
	/**
	 * Find any SPDX annotations which are in annotationsA but not in annotationsB
	 * @param annotationsA
	 * @param annotationsB
	 * @return
	 */
	public static Annotation[] findUniqueAnnotations(Annotation[] annotationsA,
			Annotation[] annotationsB) {
		List retval = Lists.newArrayList();
		if (annotationsA != null) {
			for (int i = 0; i < annotationsA.length; i++) {
				if (annotationsA[i] == null) {
					continue;
				}
				boolean found = false;
				if (annotationsB != null) {
					for (int j = 0; j < annotationsB.length; j++) {
						if (annotationsA[i].equivalent(annotationsB[j])) {
							found = true;
							break;
						}
					}
				}
				if (!found) {
					retval.add(annotationsA[i]);
				}
			}
		}
		return retval.toArray(new Annotation[retval.size()]);
	}

	/**
	 * Returns true if two arrays of SPDX elements contain equivalent elements
	 * @param elementsA
	 * @param elementsB
	 * @return
	 */
	public static boolean elementsEquivalent(RdfModelObject[] elementsA,
			RdfModelObject[] elementsB) {
		if (elementsA == null) {
			return elementsB == null;
		}
		if (elementsB == null) {
			return false;
		}
		if (elementsA.length != elementsB.length) {
			return false;
		}
		Set matchedIndexes = Sets.newHashSet();
		for (int i = 0; i < elementsA.length; i++) {
			boolean found = false;
			for (int j = 0; j < elementsB.length; j++) {
				if (!matchedIndexes.contains(j) &&
						elementsA[i].equivalent(elementsB[j])) {
					found = true;
					matchedIndexes.add(j);
					break;
				}
			}
			if (!found) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Find unique relationships that are present in relationshipsA but not relationshipsB
	 * @param relationshipsA
	 * @param relationshipsB
	 * @return
	 */
	public static Relationship[] findUniqueRelationships(
			Relationship[] relationshipsA, Relationship[] relationshipsB) {
		List retval = Lists.newArrayList();
		if (relationshipsA == null) {
			return new Relationship[0];
		}
		for (int i = 0; i < relationshipsA.length; i++) {
			if (relationshipsA[i] == null) {
				continue;
			}
			boolean found = false;
			if (relationshipsB != null) {
				for (int j = 0; j < relationshipsB.length; j++) {
					if (relationshipsA[i].equivalent(relationshipsB[j])) {
						found = true;
						break;
					}
				}
			}
			if (!found) {
				retval.add(relationshipsA[i]);
			}
		}
		return retval.toArray(new Relationship[retval.size()]);
	}
	
	/**
	 * Find unique relationships that are present in relationshipsA but not relationshipsB
	 * @param externalDocRefsA
	 * @param externalDocRefsB
	 * @return
	 * @throws InvalidSPDXAnalysisException 
	 */
	public static ExternalDocumentRef[] findUniqueExternalDocumentRefs(
			ExternalDocumentRef[] externalDocRefsA, ExternalDocumentRef[] externalDocRefsB) throws InvalidSPDXAnalysisException {
		List retval = Lists.newArrayList();
		if (externalDocRefsA == null) {
			return new ExternalDocumentRef[0];
		}
		for (int i = 0; i < externalDocRefsA.length; i++) {
			if (externalDocRefsA[i] == null) {
				continue;
			}
			boolean found = false;
			if (externalDocRefsB != null) {
				for (int j = 0; j < externalDocRefsB.length; j++) {
					if (compareStrings(externalDocRefsA[i].getSpdxDocumentNamespace(),
							externalDocRefsB[j].getSpdxDocumentNamespace()) == 0 &&
							elementsEquivalent(externalDocRefsA[i].getChecksum(),
									externalDocRefsB[j].getChecksum())) {
						found = true;
						break;
					}
				}
			}
			if (!found) {
				retval.add(externalDocRefsA[i]);
			}
		}
		return retval.toArray(new ExternalDocumentRef[retval.size()]);
	}

	/**
	 * @return
	 */
	public SpdxDocument[] getSpdxDocuments() {
		return this.spdxDocs;
	}

	/**
	 * @return
	 * @throws SpdxCompareException 
	 */
	public boolean isDocumentContentsEquals() throws SpdxCompareException {
		checkInProgress();
		return this.documentContentsEquals;
	}

	/**
	 * @return
	 * @throws SpdxCompareException 
	 */
	public boolean isSnippetsEqual() throws SpdxCompareException {
		this.checkDocsField();
		this.checkInProgress();
		return this._isSnippetsEqualsNoCheck();
	}

	/**
	 * @return
	 * @throws SpdxCompareException 
	 */
	private boolean _isSnippetsEqualsNoCheck() throws SpdxCompareException {
		Iterator>> iter = this.uniqueSnippets.entrySet().iterator();
		while (iter.hasNext()) {
			Iterator docIterator = iter.next().getValue().values().iterator();
			while (docIterator.hasNext()) {
				if (docIterator.next().length > 0) {
					return false;
				}
			}
		}
		Iterator diffIter = this.snippetComparers.values().iterator();
		while (diffIter.hasNext()) {
			if (diffIter.next().isDifferenceFound()) {
				return false;
			}
		}
		return true;
	}

	/**
	 * @return all snippet comparers
	 */
	public SpdxSnippetComparer[] getSnippetComparers() {
		return this.snippetComparers.values().toArray(
				new SpdxSnippetComparer[this.snippetComparers.values().size()]);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy