/**
 * Copyright (c) 2010, 2011 Source Auditor 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.rdfparser;

import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;

import org.spdx.rdfparser.license.AnyLicenseInfo;
import org.spdx.rdfparser.license.ExtractedLicenseInfo;
import org.spdx.rdfparser.license.LicenseInfoFactory;
import org.spdx.rdfparser.license.SpdxListedLicense;
import org.spdx.rdfparser.model.IRdfModel;
import org.spdx.spdxspreadsheet.InvalidLicenseStringException;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.graph.Triple;
import org.apache.jena.rdf.model.AnonId;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.util.iterator.ExtendedIterator;


/**
 *
 * Simple model for the SPDX Analysis document.  The document is stored in a Jena RDF
 * model which can be accessed through the model property.
 *
 * The class should be constructed using the SPDXDocumentFactory class
 *
 * The createSpdxDocument(uri) must be called first for a blank model
 *
 * The license, file, and package objects can be constructed then added to the model
 * by using the set functions.
 *
 * The non-standard licenses must contain a unique ID of the form LicenseRef-NN where NN is a
 * unique number.  The method <code>addNonStandardLicense(licenseText)</code> can be called to
 * create a new unique Non-Standard License.
 *
 * This class is provided for compatibility with version 1.2 of the libraries - it has been
 * replaced with SpdxDocumentContainer in version 2.0 and above.
 *
 * @author Gary O'Neall
 *
 */
@Deprecated
public class SPDXDocument implements SpdxRdfConstants, IModelContainer {

	public static final String POINT_EIGHT_SPDX_VERSION = "SPDX-0.8";
	public static final String POINT_NINE_SPDX_VERSION = "SPDX-0.9";
	public static final String ONE_DOT_ZERO_SPDX_VERSION = "SPDX-1.0";
	public static final String ONE_DOT_ONE_SPDX_VERSION = "SPDX-1.1";
	public static final String ONE_DOT_TWO_SPDX_VERSION = "SPDX-1.2";
	public static final String TWO_DOT_ZERO_DOT_ZERO_VERSION = "2.0.0";

	public static final String CURRENT_SPDX_VERSION = "SPDX-2.0";

	public static final String CURRENT_IMPLEMENTATION_VERSION = "2.0.2";

	static Set<String> SUPPORTED_SPDX_VERSIONS = Sets.newHashSet();

	static {
		SUPPORTED_SPDX_VERSIONS.add(CURRENT_SPDX_VERSION);
		SUPPORTED_SPDX_VERSIONS.add(POINT_EIGHT_SPDX_VERSION);
		SUPPORTED_SPDX_VERSIONS.add(POINT_NINE_SPDX_VERSION);
		SUPPORTED_SPDX_VERSIONS.add(ONE_DOT_ZERO_SPDX_VERSION);
		SUPPORTED_SPDX_VERSIONS.add(ONE_DOT_ONE_SPDX_VERSION);
		SUPPORTED_SPDX_VERSIONS.add(ONE_DOT_TWO_SPDX_VERSION);
		SUPPORTED_SPDX_VERSIONS.add(TWO_DOT_ZERO_DOT_ZERO_VERSION);
	}

	/**
	 * Keeps tract of the next license reference number when generating the license ID's for
	 * non-standard licenses
	 */
	private AtomicInteger nextLicenseRef = new AtomicInteger(1);

	/**
	 * Simple class representing an SPDX Package.  This is stored in an RDF
	 * model.
	 *
	 * This package is initialized using an existing SPDXPackage in an
	 * RDF document by constructing the package with the node representing the
	 * SPDX package.
	 *
	 * This class is provided for compatibility with version 1.2 of the library.
	 * This class is replaced by model.SpdxPackage in version 2.0 and above.
	 *
	 * @author Gary O'Neall
	 *
	 */
	@Deprecated
	public class SPDXPackage {
		private SPDXDocument enclosingSpdxDocument;
		private Node node = null;
		/**
		 * Construct a new SPDX package and populate the properties from the node
		 * @param pkgNode Node in the RDF graph representing the SPDX package
		 */
		public SPDXPackage(Node pkgNode, SPDXDocument enclosingSpdxDocument) {
			this.node = pkgNode;
			this.enclosingSpdxDocument = enclosingSpdxDocument;
		}
		/**
		 * @return the declaredName
		 * @throws InvalidSPDXAnalysisException
		 */
		public String getDeclaredName() throws InvalidSPDXAnalysisException {
			String[] declaredNames = findDocPropertieStringValues(this.node, PROP_PACKAGE_DECLARED_NAME);
			if (declaredNames == null || declaredNames.length == 0) {
				return null;
			}
			if (declaredNames.length > 1) {
				throw(new InvalidSPDXAnalysisException("More than one declared name for a package"));
			}
			return(declaredNames[0]);
		}

		/**
		 * @param declaredName the declaredName to set
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setDeclaredName(String declaredName) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_DECLARED_NAME);
			if (declaredName != null) {
				addProperty(node, PROP_PACKAGE_DECLARED_NAME, new String[] {declaredName});
			}
		}
		/**
		 * @return the fileName
		 * @throws InvalidSPDXAnalysisException
		 */
		public String getFileName() throws InvalidSPDXAnalysisException {
			String[] fileNames = findDocPropertieStringValues(this.node, PROP_PACKAGE_FILE_NAME);
			if (fileNames == null || fileNames.length == 0) {
				return null;
			}
			if (fileNames.length > 1) {
				throw(new InvalidSPDXAnalysisException("More than one machine name for a package"));
			}
			return fileNames[0];
		}
		/**
		 * @param fileName the fileName to set
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setFileName(String fileName) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_FILE_NAME);
			if (fileName != null) {
				addProperty(node, PROP_PACKAGE_FILE_NAME, new String[] {fileName});
			}
		}
		/**
		 * @return the sha1
		 * @throws InvalidSPDXAnalysisException
		 */
		public String getSha1() throws InvalidSPDXAnalysisException {

			String retval = null;
			Node p = model.getProperty(SPDX_NAMESPACE, PROP_PACKAGE_CHECKSUM).asNode();
			Triple m = Triple.createMatch(this.node, p, null);
			ExtendedIterator<Triple> tripleIter = model.getGraph().find(m);
			while (tripleIter.hasNext()) {
				Triple t = tripleIter.next();
				SPDXChecksum cksum = new SPDXChecksum(model, t.getObject());
				if (cksum.getAlgorithm().equals(SpdxRdfConstants.ALGORITHM_SHA1)) {
					retval = cksum.getValue();
				}
			}
			return retval;
		}
		/**
		 * @param sha1 the sha1 to set
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setSha1(String sha1) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_CHECKSUM);
			if (sha1 != null) {
				SPDXChecksum cksum = new SPDXChecksum(SpdxRdfConstants.ALGORITHM_SHA1, sha1);
				Resource cksumResource = cksum.createResource(model);
				Resource s = getResource(this.node);
				Property p = model.createProperty(SPDX_NAMESPACE, PROP_PACKAGE_CHECKSUM);
				s.addProperty(p, cksumResource);
			}
		}
		/**
		 * @return the sourceInfo
		 * @throws InvalidSPDXAnalysisException
		 */
		public String getSourceInfo() throws InvalidSPDXAnalysisException {
			String[] sourceInfos = findDocPropertieStringValues(this.node, PROP_PACKAGE_SOURCE_INFO);
			if (sourceInfos == null || sourceInfos.length == 0) {
				return null;
			}
			if (sourceInfos.length > 1) {
				throw(new InvalidSPDXAnalysisException("More than one source info for an SPDX package"));
			}
			return sourceInfos[0];
		}
		/**
		 * @param sourceInfo the sourceInfo to set
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setSourceInfo(String sourceInfo) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_SOURCE_INFO);
			if (sourceInfo != null) {
				addProperty(node, PROP_PACKAGE_SOURCE_INFO, new String[] {sourceInfo});
			}
		}

		/**
		 * @return Version information of the package
		 * @throws InvalidSPDXAnalysisException
		 */
		public String getVersionInfo() throws InvalidSPDXAnalysisException {
			String[] versionInfos = findDocPropertieStringValues(this.node, PROP_PACKAGE_VERSION_INFO);
			if (versionInfos == null || versionInfos.length == 0) {
				return null;
			}
			if (versionInfos.length > 1) {
				throw(new InvalidSPDXAnalysisException("More than one version info for an SPDX package"));
			}
			return versionInfos[0];
		}

		/**
		 * Set the version information of the package
		 * @param versionInfo
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setVersionInfo(String versionInfo) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_VERSION_INFO);
			if (versionInfo != null) {
				addProperty(node, PROP_PACKAGE_VERSION_INFO, new String[] {versionInfo});
			}
		}

		/**
		 * @return the declaredLicenses
		 * @throws InvalidSPDXAnalysisException
		 */
		public AnyLicenseInfo getDeclaredLicense() throws InvalidSPDXAnalysisException {
			List<AnyLicenseInfo> alLic = Lists.newArrayList();
			Node p = model.getProperty(SPDX_NAMESPACE, PROP_PACKAGE_DECLARED_LICENSE).asNode();
			Triple m = Triple.createMatch(this.node, p, null);
			ExtendedIterator<Triple> tripleIter = model.getGraph().find(m);
			while (tripleIter.hasNext()) {
				Triple t = tripleIter.next();
				alLic.add(LicenseInfoFactory.getLicenseInfoFromModel(enclosingSpdxDocument, t.getObject()));
			}
			if (alLic.size() > 1) {
				throw(new InvalidSPDXAnalysisException("Too many declared licenses"));
			}
			if (alLic.size() == 0) {
				return null;
			}
			return alLic.get(0);
		}
		/**
		 * @param declaredLicenses the declaredLicenses to set
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setDeclaredLicense(AnyLicenseInfo declaredLicense) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_DECLARED_LICENSE);
			if (declaredLicense != null) {
				Resource s = getResource(this.node);
				Property p = model.createProperty(SPDX_NAMESPACE, PROP_PACKAGE_DECLARED_LICENSE);

				Resource lic = declaredLicense.createResource(this.enclosingSpdxDocument);
				s.addProperty(p, lic);
			}
		}


		/**
		 * @return the detectedLicenses
		 * @throws InvalidSPDXAnalysisException
		 */
		public AnyLicenseInfo getConcludedLicenses() throws InvalidSPDXAnalysisException {
			List<AnyLicenseInfo> alLic = Lists.newArrayList();
			Node p = model.getProperty(SPDX_NAMESPACE, PROP_PACKAGE_CONCLUDED_LICENSE).asNode();
			Triple m = Triple.createMatch(this.node, p, null);
			ExtendedIterator<Triple> tripleIter = model.getGraph().find(m);
			while (tripleIter.hasNext()) {
				Triple t = tripleIter.next();
				alLic.add(LicenseInfoFactory.getLicenseInfoFromModel(enclosingSpdxDocument, t.getObject()));
			}
			if (alLic.size() > 1) {
				throw(new InvalidSPDXAnalysisException("Too many concluded licenses"));
			}
			if (alLic.size() == 0) {
				return null;
			}
			return alLic.get(0);
		}
		/**
		 * @param detectedLicenses the detectedLicenses to set
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setConcludedLicenses(AnyLicenseInfo detectedLicenses) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_CONCLUDED_LICENSE);
			if (detectedLicenses != null) {
				Resource s = getResource(this.node);
				Property p = model.createProperty(SPDX_NAMESPACE, PROP_PACKAGE_CONCLUDED_LICENSE);
				Resource lic = detectedLicenses.createResource(this.enclosingSpdxDocument);
				s.addProperty(p, lic);
			}
		}
		/**
		 * @return the licenseComment
		 * @throws InvalidSPDXAnalysisException
		 */
		public String getLicenseComment() throws InvalidSPDXAnalysisException {
			String[] comments = findDocPropertieStringValues(this.node, PROP_PACKAGE_LICENSE_COMMENT);
			if (comments == null || comments.length == 0) {
				return null;
			}
			if (comments.length > 1) {
				throw(new InvalidSPDXAnalysisException("More than one license comment for a package"));
			}
			return(comments[0]);
		}
		/**
		 * @param comments the license comments to set
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setLicenseComment(String comments) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_LICENSE_COMMENT);
			if (comments != null) {
				addProperty(node, PROP_PACKAGE_LICENSE_COMMENT, new String[] {comments});
			}
		}
		/**
		 * @return the declaredCopyright
		 * @throws InvalidSPDXAnalysisException
		 */
		public String getDeclaredCopyright() throws InvalidSPDXAnalysisException {
			String[] copyrights = findDocPropertieStringValues(this.node, PROP_PACKAGE_DECLARED_COPYRIGHT);
			if (copyrights == null || copyrights.length == 0) {
				return null;
			}
			if (copyrights.length > 1) {
				throw(new InvalidSPDXAnalysisException("More than one declared copyright for a package"));
			}
			return(copyrights[0]);
		}
		/**
		 * @param declaredCopyright the declaredCopyright to set
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setDeclaredCopyright(String declaredCopyright) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_DECLARED_COPYRIGHT);
			if (declaredCopyright != null) {
				addProperty(node, PROP_PACKAGE_DECLARED_COPYRIGHT, new String[] {declaredCopyright});
			}
		}
		/**
		 * @return the shortDescription
		 * @throws InvalidSPDXAnalysisException
		 */
		public String getShortDescription() throws InvalidSPDXAnalysisException {
			String[] shortDescs = findDocPropertieStringValues(this.node, PROP_PACKAGE_SHORT_DESC);
			if (shortDescs == null || shortDescs.length == 0) {
				return null;
			}
			if (shortDescs.length > 1) {
				throw(new InvalidSPDXAnalysisException("More than one short description for a package"));
			}
			return(shortDescs[0]);
		}
		/**
		 * @param shortDescription the shortDescription to set
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setShortDescription(String shortDescription) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_SHORT_DESC);
			if (shortDescription != null) {
				addProperty(node, PROP_PACKAGE_SHORT_DESC, new String[] {shortDescription});
			}
		}
		/**
		 * @return the description
		 * @throws InvalidSPDXAnalysisException
		 */
		public String getDescription() throws InvalidSPDXAnalysisException {
			String[] desc = findDocPropertieStringValues(this.node, PROP_PACKAGE_DESCRIPTION);
			if (desc == null || desc.length == 0) {
				return null;
			}
			if (desc.length > 1) {
				throw(new InvalidSPDXAnalysisException("More than one description for a package"));
			}
			return(desc[0]);
		}
		/**
		 * @param description the description to set
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setDescription(String description) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_DESCRIPTION);
			if (description != null) {
				addProperty(node, PROP_PACKAGE_DESCRIPTION, new String[] {description});
			}
		}

		/**
		 * Set the originator
		 * @param originator Either a valid originator string or NOASSERTION
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setOriginator(String originator) throws InvalidSPDXAnalysisException {
			if (originator == null) {
				removeProperties(node, PROP_PACKAGE_ORIGINATOR);
				return;
			}
			String error = SpdxVerificationHelper.verifyOriginator(originator);
			if (error != null && !error.isEmpty()) {
				throw(new InvalidSPDXAnalysisException(error));
			}
			removeProperties(node, PROP_PACKAGE_ORIGINATOR);
			addProperty(node, PROP_PACKAGE_ORIGINATOR, new String[] {originator});
		}

		/**
		 * Set the Supplier
		 * @param supplier Either a valid originator string or NOASSERTION
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setSupplier(String supplier) throws InvalidSPDXAnalysisException {
			if (supplier == null) {
				removeProperties(node, PROP_PACKAGE_SUPPLIER);
				return;
			}
			String error = SpdxVerificationHelper.verifySupplier(supplier);
			if (error != null && !error.isEmpty()) {
				throw(new InvalidSPDXAnalysisException(error));
			}
			removeProperties(node, PROP_PACKAGE_SUPPLIER);
			addProperty(node, PROP_PACKAGE_SUPPLIER, new String[] {supplier});
		}

		public String getOriginator() throws InvalidSPDXAnalysisException {
			String[] originators = findDocPropertieStringValues(this.node, PROP_PACKAGE_ORIGINATOR);
			if (originators == null || originators.length == 0) {
				return null;
			}
			if (originators.length > 1) {
				throw(new InvalidSPDXAnalysisException("More than one originator for a package"));
			}
			return(originators[0]);
		}

		public String getSupplier() throws InvalidSPDXAnalysisException {
			String[] suppliers = findDocPropertieStringValues(this.node, PROP_PACKAGE_SUPPLIER);
			if (suppliers == null || suppliers.length == 0) {
				return null;
			}
			if (suppliers.length > 1) {
				throw(new InvalidSPDXAnalysisException("More than one supplier for a package"));
			}
			return(suppliers[0]);
		}

		/**
		 * @return the files
		 * @throws InvalidSPDXAnalysisException
		 */
		public SPDXFile[] getFiles() throws InvalidSPDXAnalysisException {
			// files
			List<SPDXFile> alFiles = Lists.newArrayList();
			Node p = model.getProperty(SPDX_NAMESPACE, PROP_PACKAGE_FILE).asNode();
			Triple m = Triple.createMatch(this.node, p, null);
			ExtendedIterator<Triple> tripleIter = model.getGraph().find(m);
			while (tripleIter.hasNext()) {
				Triple t = tripleIter.next();
				alFiles.add(new SPDXFile(enclosingSpdxDocument, t.getObject()));
			}
			SPDXFile[] retval = new SPDXFile[alFiles.size()];
			return alFiles.toArray(retval);
		}
		/**
		 * @param files the files to set
		 * @throws InvalidSPDXAnalysisException
		 */
		public void setFiles(SPDXFile[] files) throws InvalidSPDXAnalysisException {
			// Delete all existing files
			List<Node> alFileNodes = Lists.newArrayList();
			Node n = model.getProperty(SPDX_NAMESPACE, PROP_PACKAGE_FILE).asNode();
			Triple m = Triple.createMatch(this.node, n, null);
			ExtendedIterator<Triple> tripleIter = model.getGraph().find(m);
			while (tripleIter.hasNext()) {
				Triple t = tripleIter.next();
				alFileNodes.add(t.getObject());
			}
			removeProperties(node, PROP_PACKAGE_FILE);
			removeProperties(getSpdxDocNode(), PROP_SPDX_FILE_REFERENCE);	// NOTE: In version 2.0, we will need to remove just the files which were in the package

			for (Node fileNode : alFileNodes) {
				model.removeAll(getResource(fileNode), null, null);
			}

			if (files != null) {
				Resource s = getResource(this.node);
				Property p = model.createProperty(SPDX_NAMESPACE, PROP_PACKAGE_FILE);
				Resource docResource = getResource(getSpdxDocNode());
				Property docP = model.createProperty(SPDX_NAMESPACE, PROP_SPDX_FILE_REFERENCE);
				for (int i = 0; i < files.length; i++) {
					Resource file = files[i].createResource(getDocument(), getDocumentNamespace() + getNextSpdxElementRef());
					s.addProperty(p, file);
					docResource.addProperty(docP, file);
				}
			}
		}
		/**
		 * Add a file to the package
		 * @param file
		 */
		public void addFile(SPDXFile file) throws InvalidSPDXAnalysisException {
			Resource s = getResource(this.node);
			Property p = model.createProperty(SPDX_NAMESPACE, PROP_PACKAGE_FILE);
			Resource docResource = getResource(getSpdxDocNode());
			Property docP = model.createProperty(SPDX_NAMESPACE, PROP_SPDX_FILE_REFERENCE);
			Resource fileResource = file.createResource(getDocument(), getDocumentNamespace() + getNextSpdxElementRef());
			s.addProperty(p, fileResource);
			docResource.addProperty(docP, fileResource);
		}

		/**
		 * Removes all SPDX files by the given name
		 * @param fileName Name of SPDX file to be removed
		 * @throws InvalidSPDXAnalysisException
		 */
		public void removeFile(String fileName) throws InvalidSPDXAnalysisException {
			List<Node> filesToRemove = Lists.newArrayList();
			Node fileNameProperty = model.getProperty(SPDX_NAMESPACE, PROP_FILE_NAME).asNode();
			Property docFileProperty = model.getProperty(SPDX_NAMESPACE, PROP_SPDX_FILE_REFERENCE);
			Property pkgFileProperty = model.createProperty(SPDX_NAMESPACE, PROP_PACKAGE_FILE);
			Resource docResource = getResource(getSpdxDocNode());
			Resource pkgResource = getResource(this.node);
			Triple m = Triple.createMatch(getSpdxDocNode(), docFileProperty.asNode(), null);
			ExtendedIterator<Triple> tripleIter = model.getGraph().find(m);
			//TODO: See if there is a more efficient search method for files
			while (tripleIter.hasNext()) {
				Triple t = tripleIter.next();
				Node fileObject = t.getObject();
				Triple fileNameMatch = Triple.createMatch(fileObject, fileNameProperty, null);
				ExtendedIterator<Triple> fileNameIterator = model.getGraph().find(fileNameMatch);
				while (fileNameIterator.hasNext()) {
					Triple fileNameTriple = fileNameIterator.next();
					String searchFileName = fileNameTriple.getObject().toString(false);
					if (searchFileName.equals(fileName)) {
						filesToRemove.add(fileObject);
					}
				}
			}
			for (int i = 0; i < filesToRemove.size(); i++) {
				// remove the references files
				RDFNode o = model.getRDFNode(filesToRemove.get(i));
				model.removeAll(docResource, docFileProperty, o);
				// remove the package files
				model.removeAll(pkgResource, pkgFileProperty, o);
			}
		}
		public String getDownloadUrl() throws InvalidSPDXAnalysisException {
			String[] urls = findDocPropertieStringValues(this.node, PROP_PACKAGE_DOWNLOAD_URL);
			if (urls == null || urls.length == 0) {
				return null;
			}
			if (urls.length > 1) {
				throw(new InvalidSPDXAnalysisException("More than one URL for a package"));
			}
			return(urls[0]);
		}

		public void setDownloadUrl(String url) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_DOWNLOAD_URL);
			if (url != null) {
				addProperty(node, PROP_PACKAGE_DOWNLOAD_URL, new String[] {url});
			}
		}

		public String getHomePage() throws InvalidSPDXAnalysisException {
			String[] urls = findDocPropertieStringValues(this.node, DOAP_NAMESPACE, PROP_PROJECT_HOMEPAGE);
			if (urls == null || urls.length == 0) {
				return null;
			}
			if (urls.length > 1) {
				throw(new InvalidSPDXAnalysisException("More than one home page for a package"));
			}
			return(urls[0]);
		}

		public void setHomePage(String url) throws InvalidSPDXAnalysisException {
			removeProperties(node, DOAP_NAMESPACE, PROP_PROJECT_HOMEPAGE);
			if (url != null) {
				addProperty(node, DOAP_NAMESPACE, PROP_PROJECT_HOMEPAGE, new String[] {url});
			}
		}

		public SpdxPackageVerificationCode getVerificationCode() throws InvalidSPDXAnalysisException {
			SpdxPackageVerificationCode retval = null;
			Node p = model.getProperty(SPDX_NAMESPACE, PROP_PACKAGE_VERIFICATION_CODE).asNode();
			Triple m = Triple.createMatch(this.node, p, null);
			ExtendedIterator<Triple> tripleIter = model.getGraph().find(m);
			while (tripleIter.hasNext()) {
				Triple t = tripleIter.next();
				retval = new SpdxPackageVerificationCode(model, t.getObject());
			}
			return retval;
		}

		public void setVerificationCode(SpdxPackageVerificationCode verificationCode) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_VERIFICATION_CODE);
			if (verificationCode != null) {
				Resource verificationCodeResource = verificationCode.createResource(model);
				Resource s = getResource(this.node);
				Property p = model.createProperty(SPDX_NAMESPACE, PROP_PACKAGE_VERIFICATION_CODE);
				s.addProperty(p, verificationCodeResource);
			}
		}
		public SPDXPackageInfo getPackageInfo() throws InvalidSPDXAnalysisException {
			return new SPDXPackageInfo(this.getDeclaredName(), this.getVersionInfo(), this.getFileName(),
					this.getSha1(), this.getSourceInfo(), this.getDeclaredLicense(),
					this.getConcludedLicenses(), this.getLicenseInfoFromFiles(),
					this.getLicenseComment(), this.getDeclaredCopyright(),
					this.getShortDescription(), this.getDescription(), this.getDownloadUrl(),
					this.getVerificationCode(), this.getSupplier(), this.getOriginator(),
					this.getHomePage());
		}

		public void setLicenseInfoFromFiles(AnyLicenseInfo[] licenseInfo) throws InvalidSPDXAnalysisException {
			removeProperties(node, PROP_PACKAGE_LICENSE_INFO_FROM_FILES);
			if (licenseInfo != null) {
				Resource s = getResource(this.node);
				Property p = model.createProperty(SPDX_NAMESPACE, PROP_PACKAGE_LICENSE_INFO_FROM_FILES);
				for (int i = 0; i < licenseInfo.length; i++) {
					Resource lic = licenseInfo[i].createResource(this.enclosingSpdxDocument);
					s.addProperty(p, lic);
				}
			}
		}

		public AnyLicenseInfo[] getLicenseInfoFromFiles() throws InvalidSPDXAnalysisException {
			List<AnyLicenseInfo> alLic = Lists.newArrayList();
			Node p = model.getProperty(SPDX_NAMESPACE, PROP_PACKAGE_LICENSE_INFO_FROM_FILES).asNode();
			Triple m = Triple.createMatch(this.node, p, null);
			ExtendedIterator<Triple> tripleIter = model.getGraph().find(m);
			while (tripleIter.hasNext()) {
				Triple t = tripleIter.next();
				alLic.add(LicenseInfoFactory.getLicenseInfoFromModel(enclosingSpdxDocument, t.getObject()));
			}
			AnyLicenseInfo[] retval = new AnyLicenseInfo[alLic.size()];
			retval = alLic.toArray(retval);
			return retval;
		}

		/**
		 * Clones a deep copy of all fields to a new SPDXPackage contained in the docToCloneTo SPDXDocument.
		 * NOTE: This will overwrite any existing SPDXPackages contained within the SPDXDocument and the SPDXDocument must not contain any extracted licenses
		 * @param docToCloneTo SPDX Document to contain the result of the clone
		 * @param packageUri URI for the SPDX Package being created
		 * @return A deep copy of this SPDXPackage contained within docToCloneTo
		 * @throws InvalidSPDXAnalysisException
		 */
		public SPDXPackage clone(SPDXDocument docToCloneTo, String packageUri) throws InvalidSPDXAnalysisException {
			if (docToCloneTo.getExtractedLicenseInfos() != null && docToCloneTo.getExtractedLicenseInfos().length > 0) {
				throw(new InvalidSPDXAnalysisException("Can not clone a package to an SPDX document with existing licenses"));
			}
			if (docToCloneTo.getFileReferences()!= null && docToCloneTo.getFileReferences().length > 0) {
				throw(new InvalidSPDXAnalysisException("Can not clone a package to an SPDX document with existing files"));
			}
			//TODO: Allow cloning of existing licenses and files by merging the extracted license infos and mapping the licenses
			SPDXPackage retval = docToCloneTo.createSpdxPackage(packageUri);
			// need to copy the non-standard licenses in case they are referenced
			ExtractedLicenseInfo[] extractedLicenseInfos = getExtractedLicenseInfos();
			if (extractedLicenseInfos != null) {
				docToCloneTo.setExtractedLicenseInfos(extractedLicenseInfos);
				docToCloneTo.initializeNextLicenseRef(extractedLicenseInfos);
			}
			retval.setConcludedLicenses(this.getConcludedLicenses().clone());
			retval.setDeclaredCopyright(this.getDeclaredCopyright());
			retval.setDeclaredLicense(this.getDeclaredLicense().clone());
			retval.setDeclaredName(this.getDeclaredName());
			retval.setDescription(this.getDescription());
			retval.setDownloadUrl(this.getDownloadUrl());
			retval.setFileName(this.getFileName());
			SPDXFile[] myFiles = this.getFiles();
			if (myFiles != null) {
				SPDXFile[] clonedFiles = new SPDXFile[myFiles.length];
				for (int i = 0; i < clonedFiles.length; i++) {
					// For the refactored code, replace the following by a call to clone, call to setId, then a call to createResource
					clonedFiles[i] = myFiles[i].clone(docToCloneTo, docToCloneTo.getDocumentNamespace() + docToCloneTo.getNextSpdxElementRef());
				}
				retval.setFiles(clonedFiles);
			}
			retval.setHomePage(this.getHomePage());
			retval.setLicenseComment(this.getLicenseComment());
			AnyLicenseInfo[] licenseInfosFromFiles = this.getLicenseInfoFromFiles();
			if (licenseInfosFromFiles != null) {
				AnyLicenseInfo[] clonedLicenseInfosFromFiles = new AnyLicenseInfo[licenseInfosFromFiles.length];
				for (int i = 0; i < clonedLicenseInfosFromFiles.length; i++) {
					clonedLicenseInfosFromFiles[i] = licenseInfosFromFiles[i].clone();
				}
				retval.setLicenseInfoFromFiles(clonedLicenseInfosFromFiles);
			}
			retval.setOriginator(this.getOriginator());
			retval.setSha1(this.getSha1());
			retval.setShortDescription(this.getShortDescription());
			retval.setSourceInfo(this.getSourceInfo());
			retval.setSupplier(this.getSupplier());
			retval.setVerificationCode(this.getVerificationCode());
			retval.setVersionInfo(this.getVersionInfo());
			// need to reset the next reference numbers in the toDocument
			return retval;
		}

		/**
		 * @return Array list of any error messages found in verifying the package model
		 */
		public List<String> verify() {
			List<String> retval = Lists.newArrayList();
			// name
			try {
				String name = this.getDeclaredName();
				if (name == null || name.isEmpty()) {
					retval.add("Missing required name for package");
				}
			} catch (InvalidSPDXAnalysisException e) {
				retval.add("Invalid name: "+e.getMessage());
			}
			// summary
			try {
				@SuppressWarnings("unused")
				String summary = this.getShortDescription();
			} catch (InvalidSPDXAnalysisException e) {
				retval.add("Invalid summary: "+e.getMessage());
			}
			// description
			try {
				@SuppressWarnings("unused")
				String description = this.getDescription();
			} catch (InvalidSPDXAnalysisException e) {
				retval.add("Invalid description: "+e.getMessage());
			}
			// download location
			try {
				String downloadLocation = this.getDownloadUrl();
				if (downloadLocation == null || downloadLocation.isEmpty()) {
					retval.add("Missing required download location for package");
				}
			} catch (InvalidSPDXAnalysisException e) {
				retval.add("Invalid download location: "+e.getMessage());
			}
			// checksum
			try {
				String checksum = this.getSha1();
				if (checksum != null && !checksum.isEmpty()) {
					String verify = SpdxVerificationHelper.verifyChecksumString(checksum);
					if (verify != null) {
						retval.add("Package checksum error: "+verify);
					}
				}
			} catch (InvalidSPDXAnalysisException e) {
				retval.add("Invalid checksum: "+e.getMessage());
			}
			// source Info - optional
			try {
				@SuppressWarnings("unused")
				String sourceInfo = this.getSourceInfo();
			} catch (InvalidSPDXAnalysisException e) {
				retval.add("Invalid package source info: "+e.getMessage());
			}

			// copyright text - mandatory
			try {
				String copyrightText = this.getDeclaredCopyright();
				if (copyrightText == null || copyrightText.isEmpty()) {
					retval.add("Missing required package copyright text");
				}
			} catch (InvalidSPDXAnalysisException e) {
				retval.add("Invalid package copyright: "+e.getMessage());
			}

			// license comments - optional
			try {
				@SuppressWarnings("unused")
				String licenseComments = this.getLicenseComment();
			} catch (InvalidSPDXAnalysisException e) {
				retval.add("Invalid license comments: "+e.getMessage());
			}

			// license declared - mandatory - 1 (need to change return values)
			try {
				AnyLicenseInfo declaredLicense = this.getDeclaredLicense();
				if (declaredLicense == null) {
					retval.add("Missing required declared license");
				} else {
					retval.addAll(declaredLicense.verify());
				}
			} catch (InvalidSPDXAnalysisException e) {
				retval.add("Invalid package declared license: "+e.getMessage());
			}

			// license concluded - mandatory - 1 (need to change return values)
			try {
				AnyLicenseInfo concludedLicense = this.getConcludedLicenses();
				if (concludedLicense == null) {
					retval.add("Missing required concluded license");
				} else {
					retval.addAll(concludedLicense.verify());
				}
			} catch (InvalidSPDXAnalysisException e) {
				retval.add("Invalid package concluded license: "+e.getMessage());
			}

			// license infos from files - mandatory - 1 or more
			try {
				AnyLicenseInfo[] licenseInfosFromFiles = this.getLicenseInfoFromFiles();
				if (licenseInfosFromFiles == null || licenseInfosFromFiles.length == 0) {
					retval.add("Missing required license information from files");
				} else {
					for (int i = 0; i < licenseInfosFromFiles.length; i++) {
						retval.addAll(licenseInfosFromFiles[i].verify());
					}
				}
			} catch (InvalidSPDXAnalysisException e) {
