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

com.phloc.commons.changelog.ChangeLogSerializer Maven / Gradle / Ivy

There is a newer version: 5.0.0
Show newest version
/**
 * Copyright (C) 2006-2015 phloc systems
 * http://www.phloc.com
 * office[at]phloc[dot]com
 *
 * 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 com.phloc.commons.changelog;

import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.phloc.commons.ValueEnforcer;
import com.phloc.commons.annotations.PresentForCodeCoverage;
import com.phloc.commons.annotations.ReturnsMutableCopy;
import com.phloc.commons.callback.INonThrowingRunnableWithParameter;
import com.phloc.commons.collections.ContainerHelper;
import com.phloc.commons.io.IInputStreamProvider;
import com.phloc.commons.io.resource.URLResource;
import com.phloc.commons.lang.ClassHelper;
import com.phloc.commons.microdom.IMicroDocument;
import com.phloc.commons.microdom.IMicroElement;
import com.phloc.commons.microdom.convert.MicroTypeConverter;
import com.phloc.commons.microdom.impl.MicroDocument;
import com.phloc.commons.microdom.serialize.MicroReader;
import com.phloc.commons.string.StringHelper;
import com.phloc.commons.string.StringParser;
import com.phloc.commons.text.impl.MultiLingualText;
import com.phloc.commons.version.Version;
import com.phloc.commons.xml.CXML;
import com.phloc.commons.xml.XMLHelper;

/**
 * This class handles the reading and writing of changelog objects.
 * 
 * @author Philip Helger
 */
@Immutable
public final class ChangeLogSerializer
{
  private static final Logger s_aLogger = LoggerFactory.getLogger (ChangeLogSerializer.class);
  private static final String DATE_FORMAT = "yyyy-MM-dd";
  private static final String ELEMENT_CHANGELOG = "changelog";
  private static final String ATTR_VERSION = "version";
  private static final String ATTR_COMPONENT = "component";
  private static final String ELEMENT_ENTRY = "entry";
  private static final String ATTR_DATE = "date";
  private static final String ATTR_ACTION = "action";
  private static final String ATTR_CATEGORY = "category";
  private static final String ATTR_INCOMPATIBLE = "incompatible";
  private static final String ELEMENT_CHANGE = "change";
  private static final String ELEMENT_ISSUE = "issue";
  private static final String ELEMENT_RELEASE = "release";

  @PresentForCodeCoverage
  @SuppressWarnings ("unused")
  private static final ChangeLogSerializer s_aInstance = new ChangeLogSerializer ();

  private static final INonThrowingRunnableWithParameter  s_aLoggingCallback = new INonThrowingRunnableWithParameter  ()
  {
    public void run (final String sError)
    {
      s_aLogger.error (sError);
    }
  };

  private ChangeLogSerializer ()
  {}

  /**
   * Read the change log resource specified by the input stream provider using
   * the default logging callback.
   * 
   * @param aISP
   *        The ISP to read from. Maybe null resulting in a
   *        null return.
   * @return null if the passed ISP was null.
   */
  @Nullable
  public static ChangeLog readChangeLog (@Nullable final IInputStreamProvider aISP)
  {
    return readChangeLog (aISP, s_aLoggingCallback);
  }

  /**
   * Read the change log resource specified by the input stream provider.
   * 
   * @param aISP
   *        The ISP to read from. Maybe null resulting in a
   *        null return.
   * @param aErrorCallback
   *        The callback that handles potential errors.
   * @return null if the passed ISP was null.
   */
  @Nullable
  public static ChangeLog readChangeLog (@Nullable final IInputStreamProvider aISP,
                                         @Nonnull final INonThrowingRunnableWithParameter  aErrorCallback)
  {
    ValueEnforcer.notNull (aErrorCallback, "ErrorCallback");

    final IMicroDocument aDoc = MicroReader.readMicroXML (aISP);
    if (aDoc == null)
      return null;

    final IMicroElement eRoot = aDoc.getDocumentElement ();
    if (eRoot == null)
      return null;

    final ChangeLog ret = new ChangeLog (eRoot.getAttribute (ATTR_VERSION), eRoot.getAttribute (ATTR_COMPONENT));
    final DateFormat aDF = new SimpleDateFormat (DATE_FORMAT);

    // Add all entries
    for (final IMicroElement eElement : eRoot.getAllChildElements ())
    {
      if (!CChangeLog.CHANGELOG_NAMESPACE_10.equals (eElement.getNamespaceURI ()))
      {
        aErrorCallback.run ("Element '" +
                            eElement.getTagName () +
                            "' has the wrong namespace URI '" +
                            eElement.getNamespaceURI () +
                            "'");
        continue;
      }

      final String sTagName = eElement.getTagName ();
      if (ELEMENT_ENTRY.equals (sTagName))
      {
        final String sDate = eElement.getAttribute (ATTR_DATE);
        final String sAction = eElement.getAttribute (ATTR_ACTION);
        final String sCategory = eElement.getAttribute (ATTR_CATEGORY);
        final String sIncompatible = eElement.getAttribute (ATTR_INCOMPATIBLE);

        Date aDate;
        try
        {
          aDate = aDF.parse (sDate);
        }
        catch (final ParseException ex)
        {
          aErrorCallback.run ("Failed to parse entry date '" + sDate + "'");
          continue;
        }
        final EChangeLogAction eAction = EChangeLogAction.getFromIDOrNull (sAction);
        if (eAction == null)
        {
          aErrorCallback.run ("Failed to parse change log action '" + sAction + "'");
          continue;
        }
        final EChangeLogCategory eCategory = EChangeLogCategory.getFromIDOrNull (sCategory);
        if (eCategory == null)
        {
          aErrorCallback.run ("Failed to parse change log category '" + sCategory + "'");
          continue;
        }
        final boolean bIsIncompatible = StringHelper.hasText (sIncompatible) && StringParser.parseBool (sIncompatible);

        final ChangeLogEntry aEntry = new ChangeLogEntry (ret, aDate, eAction, eCategory, bIsIncompatible);
        ret.addEntry (aEntry);

        final IMicroElement eChange = eElement.getFirstChildElement (CChangeLog.CHANGELOG_NAMESPACE_10, ELEMENT_CHANGE);
        if (eChange == null)
        {
          aErrorCallback.run ("No change element present!");
          continue;
        }
        final MultiLingualText aMLT = MicroTypeConverter.convertToNative (eChange, MultiLingualText.class);
        if (aMLT == null)
        {
          aErrorCallback.run ("Failed to read multi lingual text in change element!");
          continue;
        }
        aEntry.setText (aMLT);
        for (final IMicroElement eIssue : eElement.getAllChildElements (CChangeLog.CHANGELOG_NAMESPACE_10,
                                                                        ELEMENT_ISSUE))
          aEntry.addIssue (eIssue.getTextContent ());
      }
      else
        if (ELEMENT_RELEASE.equals (sTagName))
        {
          final String sDate = eElement.getAttribute (ATTR_DATE);
          final String sVersion = eElement.getAttribute (ATTR_VERSION);
          Date aDate;
          try
          {
            aDate = aDF.parse (sDate);
          }
          catch (final ParseException ex)
          {
            s_aLogger.warn ("Failed to parse release date '" + sDate + "'");
            continue;
          }
          ret.addRelease (new ChangeLogRelease (aDate, new Version (sVersion, false)));
        }
        else
          aErrorCallback.run ("Changelog contains unsupported element '" + sTagName + "!");
    }
    return ret;
  }

  @Nonnull
  @ReturnsMutableCopy
  public static Map  readAllChangeLogs ()
  {
    return readAllChangeLogs (s_aLoggingCallback);
  }

  @Nonnull
  @ReturnsMutableCopy
  public static Map  readAllChangeLogs (@Nonnull final ClassLoader aClassLoader)
  {
    return readAllChangeLogs (s_aLoggingCallback, aClassLoader);
  }

  @Nonnull
  @ReturnsMutableCopy
  public static Map  readAllChangeLogs (@Nonnull final INonThrowingRunnableWithParameter  aErrorCallback)
  {
    return readAllChangeLogs (aErrorCallback, ClassHelper.getDefaultClassLoader ());
  }

  @Nonnull
  @ReturnsMutableCopy
  public static Map  readAllChangeLogs (@Nonnull final INonThrowingRunnableWithParameter  aErrorCallback,
                                                        @Nonnull final ClassLoader aClassLoader)
  {
    ValueEnforcer.notNull (aErrorCallback, "ErrorCallback");
    ValueEnforcer.notNull (aClassLoader, "ClassLoader");

    try
    {
      final Map  ret = new HashMap  ();
      // Find all change log XML files in the classpath
      for (final URL aURL : ContainerHelper.newList (aClassLoader.getResources (CChangeLog.CHANGELOG_XML_FILENAME)))
      {
        final URLResource aRes = new URLResource (aURL);
        final ChangeLog aChangeLog = readChangeLog (aRes, aErrorCallback);
        if (aChangeLog != null)
          ret.put (aRes.getAsURI (), aChangeLog);
        else
          s_aLogger.warn ("Failed to read changelog from URL " + aURL.toExternalForm ());
      }
      return ret;
    }
    catch (final IOException ex)
    {
      // Can be thrown by getResources
      throw new IllegalStateException ("Failed to resolved changelogs", ex);
    }
  }

  @Nonnull
  public static IMicroDocument writeChangeLog (@Nonnull final ChangeLog aChangeLog)
  {
    ValueEnforcer.notNull (aChangeLog, "ChangeLog");

    final DateFormat aDF = new SimpleDateFormat (DATE_FORMAT);
    final IMicroDocument ret = new MicroDocument ();
    final IMicroElement eRoot = ret.appendElement (CChangeLog.CHANGELOG_NAMESPACE_10, ELEMENT_CHANGELOG);
    eRoot.setAttribute (XMLHelper.getXMLNSAttrName (CXML.XML_NS_PREFIX_XSI), CXML.XML_NS_XSI);
    eRoot.setAttribute (CXML.XML_NS_PREFIX_XSI + ":schemaLocation", CChangeLog.CHANGELOG_SCHEMALOCATION_10);
    eRoot.setAttribute (ATTR_VERSION, aChangeLog.getOriginalVersion ());
    if (StringHelper.hasText (aChangeLog.getComponent ()))
      eRoot.setAttribute (ATTR_COMPONENT, aChangeLog.getComponent ());

    for (final AbstractChangeLogEntry aBaseEntry : aChangeLog.getAllBaseEntries ())
    {
      if (aBaseEntry instanceof ChangeLogEntry)
      {
        final ChangeLogEntry aEntry = (ChangeLogEntry) aBaseEntry;
        final IMicroElement eEntry = eRoot.appendElement (CChangeLog.CHANGELOG_NAMESPACE_10, ELEMENT_ENTRY);
        eEntry.setAttribute (ATTR_DATE, aDF.format (aEntry.getDate ()));
        eEntry.setAttribute (ATTR_ACTION, aEntry.getAction ().getID ());
        eEntry.setAttribute (ATTR_CATEGORY, aEntry.getCategory ().getID ());
        if (aEntry.isIncompatible ())
          eEntry.setAttribute (ATTR_INCOMPATIBLE, Boolean.TRUE.toString ());
        eEntry.appendChild (MicroTypeConverter.convertToMicroElement (aEntry.getAllTexts (),
                                                                      CChangeLog.CHANGELOG_NAMESPACE_10,
                                                                      ELEMENT_CHANGE));
        for (final String sIssue : aEntry.getAllIssues ())
          eEntry.appendElement (CChangeLog.CHANGELOG_NAMESPACE_10, ELEMENT_ISSUE).appendText (sIssue);
      }
      else
      {
        // Must be a release
        final ChangeLogRelease aRelease = (ChangeLogRelease) aBaseEntry;
        final IMicroElement eRelease = eRoot.appendElement (CChangeLog.CHANGELOG_NAMESPACE_10, ELEMENT_RELEASE);
        eRelease.setAttribute (ATTR_DATE, aDF.format (aRelease.getDate ()));
        eRelease.setAttribute (ATTR_VERSION, aRelease.getVersion ().getAsString ());
      }
    }

    return ret;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy