Coverage Report - org.apache.creadur.whisker.fromxml.JDomBuilder
 
Classes in this File Line Coverage Branch Coverage Complexity
JDomBuilder
99%
116/117
100%
54/54
2.31
 
 1  
 /**
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  *  to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  *  with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *   http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License. 
 18  
  */
 19  
 package org.apache.creadur.whisker.fromxml;
 20  
 
 21  
 import java.io.IOException;
 22  
 import java.io.InputStream;
 23  
 import java.util.ArrayList;
 24  
 import java.util.Calendar;
 25  
 import java.util.Collection;
 26  
 import java.util.Collections;
 27  
 import java.util.HashMap;
 28  
 import java.util.HashSet;
 29  
 import java.util.List;
 30  
 import java.util.Map;
 31  
 import java.util.SortedSet;
 32  
 import java.util.TreeSet;
 33  
 
 34  
 import org.apache.commons.lang3.StringUtils;
 35  
 import org.apache.creadur.whisker.model.ByOrganisation;
 36  
 import org.apache.creadur.whisker.model.License;
 37  
 import org.apache.creadur.whisker.model.Organisation;
 38  
 import org.apache.creadur.whisker.model.Resource;
 39  
 import org.apache.creadur.whisker.model.WithLicense;
 40  
 import org.apache.creadur.whisker.model.WithinDirectory;
 41  
 import org.apache.creadur.whisker.model.Descriptor;
 42  
 import org.jdom.Document;
 43  
 import org.jdom.Element;
 44  
 import org.jdom.JDOMException;
 45  
 import org.jdom.input.SAXBuilder;
 46  
 
 47  
 /**
 48  
  * Builds a model from xml using JDOM.
 49  
  */
 50  338044
 public class JDomBuilder {
 51  
     
 52  
     private static final String COPYRIGHT_NOTICE_NAME = "copyright-notice";
 53  
     /**
 54  
      * 
 55  
      */
 56  
     private static final String LICENSE_ELEMENT_NAME = "license";
 57  
     /**
 58  
      * 
 59  
      */
 60  
     private static final String PRIMARY_LICENSE_NAME = "primary-license";
 61  
     /** Names the element representing an organisation */
 62  
     private static final String ORGANISATION_ELEMENT_NAME = "organisation";
 63  
     /** Names the element representing a resource */
 64  
     private static final String RESOURCE_ELEMENT_NAME = "resource";
 65  
 
 66  
     /**
 67  
      * Builds a resource.
 68  
      * @param element not null
 69  
      * @return built resource, not null
 70  
      * @throws UnexpectedElementException when element is not named 'resource'
 71  
      */
 72  
     public Resource resource(Element element) throws UnexpectedElementException {
 73  464
         if (RESOURCE_ELEMENT_NAME.equals(element.getName())) {
 74  462
             return new Resource(StringUtils.trim(element.getAttributeValue("name")), 
 75  
                     StringUtils.trim(element.getAttributeValue("notice")),
 76  
                     StringUtils.trim(element.getAttributeValue("source")));
 77  
         } else {
 78  2
             throw unexpectedElementException(element, RESOURCE_ELEMENT_NAME);
 79  
         }
 80  
     }
 81  
 
 82  
     /**
 83  
      * Builds a suitable exception when the element name is unexpected.
 84  
      * @param element, not null
 85  
      * @param expectedElement, not null
 86  
      * @return a suitable exception, not null
 87  
      */
 88  
     private UnexpectedElementException unexpectedElementException(Element element,
 89  
             final String expectedElement) {
 90  4
         return new UnexpectedElementException(expectedElement, element.getName());
 91  
     }
 92  
 
 93  
     /**
 94  
      * Builds an organisation model from xml.
 95  
      * @param element, not null
 96  
      * @return {@link Organisation} not null
 97  
      * @throws UnexpectedElementException when element is not named 'organisation'
 98  
      */
 99  
     public Organisation organisation(Element element) throws UnexpectedElementException {
 100  65284
         if (ORGANISATION_ELEMENT_NAME.equals(element.getName())) {
 101  65282
             return new Organisation(
 102  
                     element.getAttributeValue("id"), 
 103  
                     element.getAttributeValue("name"), 
 104  
                     element.getAttributeValue("url"));
 105  
         } else {
 106  2
             throw unexpectedElementException(element, ORGANISATION_ELEMENT_NAME);
 107  
         }
 108  
     }
 109  
 
 110  
     /**
 111  
      * @param element
 112  
      * @return
 113  
      */
 114  
     @SuppressWarnings("unchecked")
 115  
     public Collection<Resource> collectResources(Element element) {
 116  75438
         final Collection<Resource> resources = new TreeSet<Resource>();
 117  75438
         for (Element resourceElement: (List<Element>) element.getChildren("resource")) {
 118  448
             resources.add(new JDomBuilder().resource(resourceElement));
 119  
         }
 120  75438
         return Collections.unmodifiableCollection(resources);
 121  
     }
 122  
 
 123  
     /**
 124  
      * Finds the organisation linked by ID from the given element.
 125  
      * @param element modelled ByOrganisation, not null
 126  
      * @param organisationsById organisations identified, not null
 127  
      * @throws MissingIDException when the linked organisation is not found in the given map
 128  
      */
 129  
     public Organisation organisation(final Element element,
 130  
             final Map<String, Organisation> organisationsById) throws MissingIDException {
 131  75428
         final String id = element.getAttributeValue("id");
 132  75428
         if (organisationsById.containsKey(id)) {
 133  75424
             return organisationsById.get(id);
 134  
         } else {
 135  4
             throw new MissingIDException(ORGANISATION_ELEMENT_NAME, element.getName(), id);
 136  
         }
 137  
     }
 138  
     
 139  
     /**
 140  
      * Builds a by-organisation model from xml.
 141  
      * @param element not null
 142  
      * @param organisation not null
 143  
      * @return not null
 144  
      */
 145  
     public ByOrganisation byOrganisation(final Element element, final Organisation organisation) {
 146  75428
         return new ByOrganisation(organisation, collectResources(element));
 147  
     }
 148  
 
 149  
     /**
 150  
      * Builds a by-organisation model from xml.
 151  
      * @param byOrganisation not null
 152  
      * @param organisationsById not null 
 153  
      * @return not null
 154  
      * @throws MissingIDException when the linked organisation is not found in the given map
 155  
      */
 156  
     public ByOrganisation byOrganisation(final Element byOrganisation, 
 157  
             final Map<String, Organisation> organisationsById) throws MissingIDException  {
 158  75422
         return byOrganisation(byOrganisation, organisation(byOrganisation, organisationsById));
 159  
     }
 160  
 
 161  
     /**
 162  
      * Collects by-organisation children.
 163  
      * @param parent not null
 164  
      * @param map not null
 165  
      * @return unmodifiable set sort by natural order, not null
 166  
      */
 167  
     @SuppressWarnings("unchecked")
 168  
     public SortedSet<ByOrganisation> collectByOrganisations(final Element parent, 
 169  
             final Map<String, Organisation> map) {
 170  141864
         final SortedSet<ByOrganisation> results = new TreeSet<ByOrganisation>();
 171  141864
         if (parent != null) {
 172  76376
             for (final Element byOrgElement: (List<Element>) parent.getChildren("by-organisation")) {
 173  75416
                 results.add(byOrganisation(byOrgElement, map));
 174  
             }
 175  
         }
 176  141864
         return Collections.unmodifiableSortedSet(results);
 177  
     }
 178  
 
 179  
     /**
 180  
      * Builds a license model from xml.
 181  
      * @param element not null
 182  
      * @return not null
 183  
      */
 184  
     public License license(Element element) {
 185  65308
         final Element text = element.getChild("text");
 186  65308
         return new License("yes".equalsIgnoreCase(element.getAttributeValue("requires-source")), 
 187  
                 text == null ? "" : text.getText(), 
 188  
                 expectedParameters(element), 
 189  
                 element.getAttributeValue("id"), 
 190  
                 element.getAttributeValue("url"),
 191  
                 element.getAttributeValue("name"));
 192  
     }
 193  
     
 194  
     @SuppressWarnings("unchecked")
 195  
     private Collection<String> expectedParameters(final Element element) {
 196  65308
         final Collection<String> results = new HashSet<String>();
 197  65308
         final Element templateElement = element.getChild("template");
 198  65308
         if (templateElement != null) {
 199  6
             for (Element parameterNameElement: (List<Element>) templateElement.getChildren("parameter-name")) {
 200  6
                 results.add(parameterNameElement.getTextTrim());
 201  
             }
 202  
         }
 203  65308
         return results;
 204  
     }
 205  
 
 206  
     /**
 207  
      * Finds the license with an id matching that referenced by the element.
 208  
      * @param element not null
 209  
      * @param licenses not null
 210  
      * @return not null
 211  
      * @throws MissingIDException when referenced license isn't found in the collection
 212  
      */
 213  
     public License license(final Element element, final Map<String, License> licenses) throws MissingIDException {
 214  75654
         final String id = element.getAttributeValue("id");
 215  75654
         if (licenses.containsKey(id)) {
 216  75650
             return licenses.get(id);
 217  
         } else {
 218  4
             throw new MissingIDException(LICENSE_ELEMENT_NAME, element.getName(), id);
 219  
         }
 220  
     }
 221  
 
 222  
     /**
 223  
      * Builds a with-license model from xml.
 224  
      * @param element not null
 225  
      * @param licenses not null
 226  
      * @param organisations not null
 227  
      * @return
 228  
      * @throws MissingIDException when referenced license isn't found in the collection
 229  
      */
 230  
     public WithLicense withLicense(Element element,
 231  
             Map<String, License> licenses,
 232  
             Map<String, Organisation> organisations) throws MissingIDException  {
 233  75648
         return new WithLicense(license(element, licenses), copyrightNotice(element), 
 234  
                 parameters(element), collectByOrganisations(element, organisations));
 235  
     }
 236  
     
 237  
     /**
 238  
      * Extracts copyright notice content from with-license.
 239  
      * @param element not null
 240  
      * @return not null
 241  
      */
 242  
     private String copyrightNotice(final Element element) {
 243  
         final String result;
 244  75648
         final Element copyrightNoticeElement = element.getChild(COPYRIGHT_NOTICE_NAME);
 245  75648
         if (copyrightNoticeElement == null) {
 246  75644
             result = null;
 247  
         } else {
 248  4
             result = copyrightNoticeElement.getTextTrim();
 249  
         }
 250  75648
         return result;
 251  
     }
 252  
 
 253  
     /**
 254  
      * Builds a list of parameter values by name.
 255  
      * @param element not null
 256  
      * @return parameter values indexed by value, not null
 257  
      * @throws DuplicateElementException when two parameters shared the same name
 258  
      */
 259  
     @SuppressWarnings("unchecked")
 260  
     public Map<String, String> parameters(Element element) throws DuplicateElementException {
 261  75666
         final Map<String, String> results = new HashMap<String, String>();
 262  75666
         final Element licenseParametersElement = element.getChild("license-parameters");
 263  75666
         if (licenseParametersElement != null) {
 264  274
             for (Element parameterElement: (List<Element>) licenseParametersElement.getChildren("parameter")) {
 265  16518
                 final String name = parameterElement.getChild("name").getTextTrim();
 266  16518
                 if (results.containsKey(name)) {
 267  2
                     throw new DuplicateElementException("Duplicate parameter '" + name + "'");
 268  
                 }
 269  16516
                 results.put(name, 
 270  
                         parameterElement.getChild("value").getTextTrim());
 271  16516
             }   
 272  
         }
 273  75664
         return results;
 274  
     }
 275  
 
 276  
     /**
 277  
      * Collects child with-licenses.
 278  
      * @param licenses not null
 279  
      * @param organisations not null
 280  
      * @param parent not null
 281  
      * @return not null, possibly empty
 282  
      */
 283  
     @SuppressWarnings("unchecked")
 284  
     public Collection<WithLicense> withLicenses(Map<String, License> licenses,
 285  
             Map<String, Organisation> organisations, Element parent) {
 286  66202
         final List<WithLicense> results = new ArrayList<WithLicense>();
 287  66202
         for (Element withLicenseElement: (List<Element>) parent.getChildren("with-license")) {
 288  75380
             results.add(new JDomBuilder().withLicense(withLicenseElement, licenses, organisations));
 289  
         }
 290  66202
         Collections.sort(results);
 291  66202
         return results;
 292  
     }
 293  
 
 294  
     /**
 295  
      * Collects child organisations of public domain.
 296  
      * @param organisations not null
 297  
      * @param parent not null
 298  
      * @return not null, possibly null
 299  
      */
 300  
     public Collection<ByOrganisation> publicDomain(
 301  
             final Map<String, Organisation> organisations, final Element parent) {
 302  66202
         return new JDomBuilder().collectByOrganisations(parent.getChild("public-domain"), organisations);
 303  
     }
 304  
 
 305  
     /**
 306  
      * Builds a within directory model from XML.
 307  
      * @param element not null
 308  
      * @param licenses not null
 309  
      * @param organisations not null
 310  
      * @return not null
 311  
      */
 312  
     public WithinDirectory withinDirectory(Element element,
 313  
             Map<String, License> licenses,
 314  
             Map<String, Organisation> organisations) {
 315  65690
         return new WithinDirectory(element.getAttributeValue("dir"), 
 316  
                 withLicenses(licenses, organisations, element), publicDomain(organisations, element));
 317  
     }
 318  
 
 319  
     /**
 320  
      * Collects organisation definitions within document.
 321  
      * @param document, not null
 322  
      * @return organisations indexed by id, not null possibly empty
 323  
      */
 324  
     public Map<String, Organisation> mapOrganisations(Document document) {
 325  516
         final Map<String, Organisation> organisationsById = new HashMap<String, Organisation>();
 326  
         
 327  516
         final Element childOrganisations = document.getRootElement().getChild("organisations");
 328  516
         if (childOrganisations != null) {
 329  
             @SuppressWarnings("unchecked")
 330  510
             final List<Element> organisations = (List<Element>) childOrganisations.getChildren("organisation");
 331  510
             for (final Element element: organisations) {
 332  65280
                 new JDomBuilder().organisation(element).storeIn(organisationsById);
 333  
             }
 334  
         }
 335  516
         return Collections.unmodifiableMap(organisationsById);
 336  
     }
 337  
 
 338  
     /**
 339  
      * Collects license definitions within document.
 340  
      * @param document, not null
 341  
      * @return licenses, indexed by id, not null, possibly empty
 342  
      */
 343  
     public Map<String, License> mapLicenses(Document document) {
 344  518
         final Map<String, License> results = new HashMap<String, License>();
 345  518
         final Element licensesChild = document.getRootElement().getChild("licenses");
 346  518
         if (licensesChild != null) {
 347  
             @SuppressWarnings("unchecked")
 348  514
             final List<Element> children = (List<Element>) licensesChild.getChildren();
 349  514
             for (final Element element: children) {
 350  65282
                 new JDomBuilder().license(element).storeIn(results);
 351  
             }
 352  
         }
 353  518
         return Collections.unmodifiableMap(results);
 354  
 
 355  
     }
 356  
 
 357  
     /**
 358  
      * Finds the primary license for the given document from the given licenses.
 359  
      * @param document not null
 360  
      * @param licenses not null
 361  
      * @return not null
 362  
      */
 363  
     public License primaryLicense(Document document,
 364  
             Map<String, License> licenses) {
 365  6
         final String idAttributeValue = getPrimaryLicenseElement(document).getAttributeValue("id");
 366  6
         final License results = licenses.get(idAttributeValue);
 367  6
         if (results == null) {
 368  2
             throw new MissingIDException(LICENSE_ELEMENT_NAME, PRIMARY_LICENSE_NAME, idAttributeValue);
 369  
         }
 370  4
         return results;
 371  
     }
 372  
 
 373  
     /**
 374  
      * Gets the element representing the primary license.
 375  
      * @param document not null
 376  
      * @return not null
 377  
      */
 378  
     private Element getPrimaryLicenseElement(final Document document) {
 379  12
         return document.getRootElement().getChild(PRIMARY_LICENSE_NAME);
 380  
     }
 381  
 
 382  
     /**
 383  
      * Gets the additional primary copyright notice 
 384  
      * from the document.
 385  
      * @param document not null
 386  
      * @return optional primary copyright notice, possibly null
 387  
      */
 388  
     public String primaryCopyrightNotice(final Document document) {
 389  
         final String result;
 390  6
         final Element copyrightElement = 
 391  
                 getPrimaryLicenseElement(document).getChild(COPYRIGHT_NOTICE_NAME);
 392  6
         if (copyrightElement == null) {
 393  2
             result = null;
 394  
         } else {
 395  4
             result = copyrightElement.getTextTrim();
 396  
         }
 397  6
         return result;
 398  
     }
 399  
 
 400  
     
 401  
     /**
 402  
      * Collects notices in the given documents.
 403  
      * @param document, not null
 404  
      * @return notices indexed by id, immutable, not null, possibly empty
 405  
      */
 406  
     public Map<String, String> mapNotices(Document document) {
 407  518
         final Map<String, String> results = new HashMap<String, String>();
 408  518
         final Element noticesElement = document.getRootElement().getChild("notices");
 409  518
         if (noticesElement != null){
 410  
             @SuppressWarnings("unchecked")
 411  512
             final List<Element> children = (List<Element>) noticesElement.getChildren();
 412  512
             for (final Element element: children) {
 413  65280
                 results.put(element.getAttributeValue("id"), element.getTextTrim());
 414  
             }
 415  
         }
 416  518
         return Collections.unmodifiableMap(results);
 417  
 
 418  
     }
 419  
 
 420  
     /**
 421  
      * Retrieves the text of the primary notice.
 422  
      * @param document, not null
 423  
      * @return the text of the primary notice, 
 424  
      * or null when there is no primary notice
 425  
      */
 426  
     public String primaryNotice(Document document) {
 427  
         final String result;
 428  8
         final Element primaryNoticeElement = document.getRootElement().getChild("primary-notice");
 429  8
         if (primaryNoticeElement == null) {
 430  4
             result = null;
 431  
         } else {
 432  4
             result = primaryNoticeElement.getText()
 433  
                 .replace("${year}", Integer.toString(Calendar.getInstance().get(Calendar.YEAR)));
 434  
         }
 435  8
         return result;
 436  
     }
 437  
 
 438  
     /**
 439  
      * Retrieves the ID of the primary organisation.
 440  
      * @param document, not null
 441  
      * @return the id of the primary organisation when set,
 442  
      * otherwise null
 443  
      */
 444  
     public String primaryOrganisationId(final Document document) {
 445  
         final String result;
 446  6
         final Element primaryOrganisationElement = document.getRootElement().getChild("primary-organisation");
 447  6
         if (primaryOrganisationElement == null) {
 448  4
             result = null;
 449  
         } else {
 450  2
             result = primaryOrganisationElement.getAttributeValue("id");
 451  
         }
 452  6
         return result;
 453  
     }    
 454  
 
 455  
     private WithinDirectory directory(final Element element, final Map<String, License> licenses,
 456  
             final Map<String, Organisation> organisations) {
 457  65284
         return new JDomBuilder().withinDirectory(element, licenses, organisations);
 458  
     }
 459  
 
 460  
     /**
 461  
      * Collects contents of the document.
 462  
      * @param document not null
 463  
      * @return not null, possibly empty
 464  
      * @throws DuplicateElementException when dir names are not unique
 465  
      */
 466  
     @SuppressWarnings("PMD.EmptyIfStmt")
 467  
     public Collection<WithinDirectory> collectContents(final Document document, final Map<String, License> licenses,
 468  
             final Map<String, Organisation> organisations) throws DuplicateElementException {
 469  516
         final Collection<WithinDirectory> results = new TreeSet<WithinDirectory>();
 470  
         @SuppressWarnings("unchecked")
 471  516
         final List<Element> children = document.getRootElement().getChildren("within");
 472  516
         for (Element element: children) {
 473  65284
             if (results.add(directory(element, licenses, organisations))) {
 474  
                 // fine
 475  
             } else {
 476  2
                 throw new DuplicateElementException("Duplicate parameter '" + element.getAttribute("dir").getValue() + "'");
 477  
             }
 478  
         }
 479  514
         return results;
 480  
     }
 481  
     
 482  
     /**
 483  
      * Builds work from the given document.
 484  
      * @param document not null
 485  
      * @return not null
 486  
      */
 487  
     public Descriptor build(final Document document) {
 488  2
         final Map<String, Organisation> organisations = mapOrganisations(document);
 489  2
         final Map<String, License> licenses = mapLicenses(document);
 490  2
         final Map<String, String> notices = mapNotices(document);
 491  2
         final License primaryLicense = primaryLicense(document, licenses);
 492  2
         final String primaryCopyrightNotice = primaryCopyrightNotice(document);
 493  2
         final String primaryNotice = primaryNotice(document);
 494  2
         final String primaryOrganisationId = primaryOrganisationId(document);
 495  2
         final Collection<WithinDirectory> contents = collectContents(document, licenses, organisations); 
 496  2
         return new Descriptor(primaryLicense, primaryCopyrightNotice,
 497  
                 primaryOrganisationId, primaryNotice, 
 498  
                 licenses, notices, organisations, contents);
 499  
     }
 500  
     
 501  
     public Descriptor build(final InputStream xmlStream) throws JDOMException, IOException {
 502  0
         return build(new SAXBuilder().build(xmlStream));
 503  
     }
 504  
 }