JDomBuilder.java
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.creadur.whisker.fromxml;
import org.apache.commons.lang3.StringUtils;
import org.apache.creadur.whisker.model.*;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* Builds a model from xml using JDOM.
*/
public class JDomBuilder {
private static final String COPYRIGHT_NOTICE_NAME = "copyright-notice";
/**
*
*/
private static final String LICENSE_ELEMENT_NAME = "license";
/**
*
*/
private static final String PRIMARY_LICENSE_NAME = "primary-license";
/** Names the element representing an organisation */
private static final String ORGANISATION_ELEMENT_NAME = "organisation";
/** Names the element representing a resource */
private static final String RESOURCE_ELEMENT_NAME = "resource";
/**
* Builds a resource.
*
* @param element
* not null
* @return built resource, not null
* @throws UnexpectedElementException
* when element is not named 'resource'
*/
public Resource resource(Element element) throws UnexpectedElementException {
if (RESOURCE_ELEMENT_NAME.equals(element.getName())) {
return new Resource(StringUtils.trim(element
.getAttributeValue("name")), StringUtils.trim(element
.getAttributeValue("notice")), StringUtils.trim(element
.getAttributeValue("source")));
} else {
throw unexpectedElementException(element, RESOURCE_ELEMENT_NAME);
}
}
/**
* Builds a suitable exception when the element name is unexpected.
*
* @param element
* , not null
* @param expectedElement
* , not null
* @return a suitable exception, not null
*/
private UnexpectedElementException unexpectedElementException(
Element element, final String expectedElement) {
return new UnexpectedElementException(expectedElement,
element.getName());
}
/**
* Builds an organisation model from xml.
*
* @param element
* , not null
* @return {@link Organisation} not null
* @throws UnexpectedElementException
* when element is not named 'organisation'
*/
public Organisation organisation(Element element)
throws UnexpectedElementException {
if (ORGANISATION_ELEMENT_NAME.equals(element.getName())) {
return new Organisation(element.getAttributeValue("id"),
element.getAttributeValue("name"),
element.getAttributeValue("url"));
} else {
throw unexpectedElementException(element, ORGANISATION_ELEMENT_NAME);
}
}
/**
* @param element
* JDOM element to collect.
* @return collected resources.
*/
@SuppressWarnings("unchecked")
public Collection<Resource> collectResources(Element element) {
final Collection<Resource> resources = new TreeSet<Resource>();
for (Element resourceElement : (List<Element>) element
.getChildren("resource")) {
resources.add(new JDomBuilder().resource(resourceElement));
}
return Collections.unmodifiableCollection(resources);
}
/**
* @return Finds the organisation linked by ID from the given element.
* @param element
* modelled ByOrganisation, not null
* @param organisationsById
* organisations identified, not null
* @throws MissingIDException
* when the linked organisation is not found in the given map
*/
public Organisation organisation(final Element element,
final Map<String, Organisation> organisationsById)
throws MissingIDException {
final String id = element.getAttributeValue("id");
if (organisationsById.containsKey(id)) {
return organisationsById.get(id);
} else {
throw new MissingIDException(ORGANISATION_ELEMENT_NAME,
element.getName(), id);
}
}
/**
* Builds a by-organisation model from xml.
*
* @param element
* not null
* @param organisation
* not null
* @return not null
*/
public ByOrganisation byOrganisation(final Element element,
final Organisation organisation) {
return new ByOrganisation(organisation, collectResources(element));
}
/**
* Builds a by-organisation model from xml.
*
* @param byOrganisation
* not null
* @param organisationsById
* not null
* @return not null
* @throws MissingIDException
* when the linked organisation is not found in the given map
*/
public ByOrganisation byOrganisation(final Element byOrganisation,
final Map<String, Organisation> organisationsById)
throws MissingIDException {
return byOrganisation(byOrganisation,
organisation(byOrganisation, organisationsById));
}
/**
* Collects by-organisation children.
*
* @param parent
* not null
* @param map
* not null
* @return unmodifiable set sort by natural order, not null
*/
@SuppressWarnings("unchecked")
public SortedSet<ByOrganisation> collectByOrganisations(
final Element parent, final Map<String, Organisation> map) {
final SortedSet<ByOrganisation> results = new TreeSet<ByOrganisation>();
if (parent != null) {
for (final Element byOrgElement : (List<Element>) parent
.getChildren("by-organisation")) {
results.add(byOrganisation(byOrgElement, map));
}
}
return Collections.unmodifiableSortedSet(results);
}
/**
* Builds a license model from xml.
*
* @param element
* not null
* @return not null
*/
public License license(Element element) {
final Element text = element.getChild("text");
return new License("yes".equalsIgnoreCase(element
.getAttributeValue("requires-source")), text == null ? ""
: text.getText(), expectedParameters(element),
element.getAttributeValue("id"),
element.getAttributeValue("url"),
element.getAttributeValue("name"));
}
@SuppressWarnings("unchecked")
private Collection<String> expectedParameters(final Element element) {
final Collection<String> results = new HashSet<String>();
final Element templateElement = element.getChild("template");
if (templateElement != null) {
for (Element parameterNameElement : (List<Element>) templateElement
.getChildren("parameter-name")) {
results.add(parameterNameElement.getTextTrim());
}
}
return results;
}
/**
* Finds the license with an id matching that referenced by the element.
*
* @param element
* not null
* @param licenses
* not null
* @return not null
* @throws MissingIDException
* when referenced license isn't found in the collection
*/
public License license(final Element element,
final Map<String, License> licenses) throws MissingIDException {
final String id = element.getAttributeValue("id");
if (licenses.containsKey(id)) {
return licenses.get(id);
} else {
throw new MissingIDException(LICENSE_ELEMENT_NAME,
element.getName(), id);
}
}
/**
* @return Builds a with-license model from xml.
* @param element
* not null
* @param licenses
* not null
* @param organisations
* not null
*
* @throws MissingIDException
* when referenced license isn't found in the collection
*/
public WithLicense withLicense(Element element,
Map<String, License> licenses,
Map<String, Organisation> organisations) throws MissingIDException {
return new WithLicense(license(element, licenses),
copyrightNotice(element), parameters(element),
collectByOrganisations(element, organisations));
}
/**
* Extracts copyright notice content from with-license.
*
* @param element
* not null
* @return not null
*/
private String copyrightNotice(final Element element) {
final String result;
final Element copyrightNoticeElement = element
.getChild(COPYRIGHT_NOTICE_NAME);
if (copyrightNoticeElement == null) {
result = null;
} else {
result = copyrightNoticeElement.getTextTrim();
}
return result;
}
/**
* Builds a list of parameter values by name.
*
* @param element
* not null
* @return parameter values indexed by value, not null
* @throws DuplicateElementException
* when two parameters shared the same name
*/
@SuppressWarnings("unchecked")
public Map<String, String> parameters(Element element)
throws DuplicateElementException {
final Map<String, String> results = new HashMap<String, String>();
final Element licenseParametersElement = element
.getChild("license-parameters");
if (licenseParametersElement != null) {
for (Element parameterElement : (List<Element>) licenseParametersElement
.getChildren("parameter")) {
final String name = parameterElement.getChild("name")
.getTextTrim();
if (results.containsKey(name)) {
throw new DuplicateElementException("Duplicate parameter '"
+ name + "'");
}
results.put(name, parameterElement.getChild("value")
.getTextTrim());
}
}
return results;
}
/**
* Collects child with-licenses.
*
* @param licenses
* not null
* @param organisations
* not null
* @param parent
* not null
* @return not null, possibly empty
*/
@SuppressWarnings("unchecked")
public Collection<WithLicense> withLicenses(Map<String, License> licenses,
Map<String, Organisation> organisations, Element parent) {
final List<WithLicense> results = new ArrayList<WithLicense>();
for (Element withLicenseElement : (List<Element>) parent
.getChildren("with-license")) {
results.add(new JDomBuilder().withLicense(withLicenseElement,
licenses, organisations));
}
Collections.sort(results);
return results;
}
/**
* Collects child organisations of public domain.
*
* @param organisations
* not null
* @param parent
* not null
* @return not null, possibly null
*/
public Collection<ByOrganisation> publicDomain(
final Map<String, Organisation> organisations, final Element parent) {
return new JDomBuilder().collectByOrganisations(
parent.getChild("public-domain"), organisations);
}
/**
* Builds a within directory model from XML.
*
* @param element
* not null
* @param licenses
* not null
* @param organisations
* not null
* @return not null
*/
public WithinDirectory withinDirectory(Element element,
Map<String, License> licenses,
Map<String, Organisation> organisations) {
return new WithinDirectory(element.getAttributeValue("dir"),
withLicenses(licenses, organisations, element), publicDomain(
organisations, element));
}
/**
* Collects organisation definitions within document.
*
* @param document
* , not null
* @return organisations indexed by id, not null possibly empty
*/
public Map<String, Organisation> mapOrganisations(Document document) {
final Map<String, Organisation> organisationsById = new HashMap<String, Organisation>();
final Element childOrganisations = document.getRootElement().getChild(
"organisations");
if (childOrganisations != null) {
@SuppressWarnings("unchecked")
final List<Element> organisations = (List<Element>) childOrganisations
.getChildren("organisation");
for (final Element element : organisations) {
new JDomBuilder().organisation(element).storeIn(
organisationsById);
}
}
return Collections.unmodifiableMap(organisationsById);
}
/**
* Collects license definitions within document.
*
* @param document
* , not null
* @return licenses, indexed by id, not null, possibly empty
*/
public Map<String, License> mapLicenses(Document document) {
final Map<String, License> results = new HashMap<String, License>();
final Element licensesChild = document.getRootElement().getChild(
"licenses");
if (licensesChild != null) {
@SuppressWarnings("unchecked")
final List<Element> children = (List<Element>) licensesChild
.getChildren();
for (final Element element : children) {
new JDomBuilder().license(element).storeIn(results);
}
}
return Collections.unmodifiableMap(results);
}
/**
* Finds the primary license for the given document from the given licenses.
*
* @param document
* not null
* @param licenses
* not null
* @return not null
*/
public License primaryLicense(Document document,
Map<String, License> licenses) {
final String idAttributeValue = getPrimaryLicenseElement(document)
.getAttributeValue("id");
final License results = licenses.get(idAttributeValue);
if (results == null) {
throw new MissingIDException(LICENSE_ELEMENT_NAME,
PRIMARY_LICENSE_NAME, idAttributeValue);
}
return results;
}
/**
* Gets the element representing the primary license.
*
* @param document
* not null
* @return not null
*/
private Element getPrimaryLicenseElement(final Document document) {
return document.getRootElement().getChild(PRIMARY_LICENSE_NAME);
}
/**
* Gets the additional primary copyright notice from the document.
*
* @param document
* not null
* @return optional primary copyright notice, possibly null
*/
public String primaryCopyrightNotice(final Document document) {
final String result;
final Element copyrightElement = getPrimaryLicenseElement(document)
.getChild(COPYRIGHT_NOTICE_NAME);
if (copyrightElement == null) {
result = null;
} else {
result = copyrightElement.getTextTrim();
}
return result;
}
/**
* Collects notices in the given documents.
*
* @param document
* , not null
* @return notices indexed by id, immutable, not null, possibly empty
*/
public Map<String, String> mapNotices(Document document) {
final Map<String, String> results = new HashMap<String, String>();
final Element noticesElement = document.getRootElement().getChild(
"notices");
if (noticesElement != null) {
@SuppressWarnings("unchecked")
final List<Element> children = (List<Element>) noticesElement
.getChildren();
for (final Element element : children) {
results.put(element.getAttributeValue("id"),
element.getTextTrim());
}
}
return Collections.unmodifiableMap(results);
}
/**
* Retrieves the text of the primary notice.
*
* @param document
* , not null
* @return the text of the primary notice, or null when there is no primary
* notice
*/
public String primaryNotice(Document document) {
final String result;
final Element primaryNoticeElement = document.getRootElement()
.getChild("primary-notice");
if (primaryNoticeElement == null) {
result = null;
} else {
result = primaryNoticeElement.getText()
.replace(
"${year}",
Integer.toString(Calendar.getInstance().get(
Calendar.YEAR)));
}
return result;
}
/**
* Retrieves the ID of the primary organisation.
*
* @param document
* , not null
* @return the id of the primary organisation when set, otherwise null
*/
public String primaryOrganisationId(final Document document) {
final String result;
final Element primaryOrganisationElement = document.getRootElement()
.getChild("primary-organisation");
if (primaryOrganisationElement == null) {
result = null;
} else {
result = primaryOrganisationElement.getAttributeValue("id");
}
return result;
}
private WithinDirectory directory(final Element element,
final Map<String, License> licenses,
final Map<String, Organisation> organisations) {
return new JDomBuilder().withinDirectory(element, licenses,
organisations);
}
/**
* Collects contents of the document.
*
* @param document
* not null
* @param licenses
* not null
* @param organisations
* not null
* @return not null, possibly empty
* @throws DuplicateElementException
* when directory names are not unique
*/
public Collection<WithinDirectory> collectContents(final Document document,
final Map<String, License> licenses,
final Map<String, Organisation> organisations)
throws DuplicateElementException {
final Collection<WithinDirectory> results = new TreeSet<WithinDirectory>();
@SuppressWarnings("unchecked")
final List<Element> children = document.getRootElement().getChildren(
"within");
for (Element element : children) {
boolean addedSuccessfully = results.add(directory(element,
licenses, organisations));
if (!addedSuccessfully) {
throw new DuplicateElementException("Duplicate parameter '"
+ element.getAttribute("dir").getValue() + "'");
}
}
return results;
}
/**
* Builds work from the given document.
*
* @param document
* not null
* @return not null
*/
public Descriptor build(final Document document) {
final Map<String, Organisation> organisations = mapOrganisations(document);
final Map<String, License> licenses = mapLicenses(document);
final Map<String, String> notices = mapNotices(document);
final License primaryLicense = primaryLicense(document, licenses);
final String primaryCopyrightNotice = primaryCopyrightNotice(document);
final String primaryNotice = primaryNotice(document);
final String primaryOrganisationId = primaryOrganisationId(document);
final Collection<WithinDirectory> contents = collectContents(document,
licenses, organisations);
return new Descriptor(primaryLicense, primaryCopyrightNotice,
primaryOrganisationId, primaryNotice, licenses, notices,
organisations, contents);
}
public Descriptor build(final InputStream xmlStream) throws JDOMException,
IOException {
return build(new SAXBuilder().build(xmlStream));
}
}