com.helger.xml.util.changelog.ChangeLogSerializer Maven / Gradle / Ivy
/**
* Copyright (C) 2014-2016 Philip Helger (www.helger.com)
* philip[at]helger[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.helger.xml.util.changelog;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.xml.XMLConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.PresentForCodeCoverage;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.changelog.AbstractChangeLogEntry;
import com.helger.commons.changelog.CChangeLog;
import com.helger.commons.changelog.ChangeLog;
import com.helger.commons.changelog.ChangeLogEntry;
import com.helger.commons.changelog.ChangeLogRelease;
import com.helger.commons.changelog.EChangeLogAction;
import com.helger.commons.changelog.EChangeLogCategory;
import com.helger.commons.changelog.IChangeLogSerializerCallback;
import com.helger.commons.changelog.LoggingChangeLogSerializerCallback;
import com.helger.commons.collection.CollectionHelper;
import com.helger.commons.collection.ext.CommonsHashMap;
import com.helger.commons.collection.ext.ICommonsMap;
import com.helger.commons.datetime.DateTimeFormatterCache;
import com.helger.commons.io.IHasInputStream;
import com.helger.commons.io.resource.URLResource;
import com.helger.commons.lang.ClassLoaderHelper;
import com.helger.commons.lang.IHasClassLoader;
import com.helger.commons.string.StringHelper;
import com.helger.commons.string.StringParser;
import com.helger.commons.text.MultilingualText;
import com.helger.commons.version.Version;
import com.helger.xml.CXML;
import com.helger.xml.microdom.IMicroDocument;
import com.helger.xml.microdom.IMicroElement;
import com.helger.xml.microdom.MicroDocument;
import com.helger.xml.microdom.convert.MicroTypeConverter;
import com.helger.xml.microdom.serialize.MicroReader;
/**
* 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);
// "u" == year, "y" == year of era
private static final DateTimeFormatter DF = DateTimeFormatterCache.getDateTimeFormatterStrict ("uuuu-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";
private static final IChangeLogSerializerCallback s_aDefaultCallback = new LoggingChangeLogSerializerCallback ();
@PresentForCodeCoverage
private static final ChangeLogSerializer s_aInstance = new ChangeLogSerializer ();
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 IHasInputStream aISP)
{
return readChangeLog (aISP, s_aDefaultCallback);
}
/**
* 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 IHasInputStream aISP,
@Nonnull final IChangeLogSerializerCallback 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.getAttributeValue (ATTR_VERSION),
eRoot.getAttributeValue (ATTR_COMPONENT));
// Add all entries
for (final IMicroElement eElement : eRoot.getAllChildElements ())
{
if (!CChangeLog.CHANGELOG_NAMESPACE_10.equals (eElement.getNamespaceURI ()))
{
aErrorCallback.accept ("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.getAttributeValue (ATTR_DATE);
final String sAction = eElement.getAttributeValue (ATTR_ACTION);
final String sCategory = eElement.getAttributeValue (ATTR_CATEGORY);
final String sIncompatible = eElement.getAttributeValue (ATTR_INCOMPATIBLE);
LocalDate aLocalDate;
try
{
aLocalDate = DF.parse (sDate, LocalDate::from);
}
catch (final DateTimeParseException ex)
{
aErrorCallback.accept ("Failed to parse entry date '" + sDate + "'");
continue;
}
final EChangeLogAction eAction = EChangeLogAction.getFromIDOrNull (sAction);
if (eAction == null)
{
aErrorCallback.accept ("Failed to parse change log action '" + sAction + "'");
continue;
}
final EChangeLogCategory eCategory = EChangeLogCategory.getFromIDOrNull (sCategory);
if (eCategory == null)
{
aErrorCallback.accept ("Failed to parse change log category '" + sCategory + "'");
continue;
}
final boolean bIsIncompatible = StringHelper.hasText (sIncompatible) && StringParser.parseBool (sIncompatible);
final ChangeLogEntry aEntry = new ChangeLogEntry (ret, aLocalDate, eAction, eCategory, bIsIncompatible);
ret.addEntry (aEntry);
final IMicroElement eChange = eElement.getFirstChildElement (CChangeLog.CHANGELOG_NAMESPACE_10, ELEMENT_CHANGE);
if (eChange == null)
{
aErrorCallback.accept ("No change element present!");
continue;
}
final MultilingualText aMLT = MicroTypeConverter.convertToNative (eChange, MultilingualText.class);
if (aMLT == null)
{
aErrorCallback.accept ("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.getAttributeValue (ATTR_DATE);
final String sVersion = eElement.getAttributeValue (ATTR_VERSION);
LocalDate aLocalDate;
try
{
aLocalDate = DF.parse (sDate, LocalDate::from);
}
catch (final DateTimeParseException ex)
{
s_aLogger.warn ("Failed to parse release date '" + sDate + "'");
continue;
}
ret.addRelease (new ChangeLogRelease (aLocalDate, Version.parse (sVersion, false)));
}
else
aErrorCallback.accept ("Changelog contains unsupported element '" + sTagName + "!");
}
return ret;
}
@Nonnull
@ReturnsMutableCopy
public static ICommonsMap readAllChangeLogs ()
{
return readAllChangeLogs (s_aDefaultCallback);
}
@Nonnull
@ReturnsMutableCopy
public static ICommonsMap readAllChangeLogs (@Nullable final ClassLoader aClassLoader)
{
return readAllChangeLogs (s_aDefaultCallback, aClassLoader);
}
@Nonnull
@ReturnsMutableCopy
public static ICommonsMap readAllChangeLogs (@Nonnull final IHasClassLoader aClassLoaderProvider)
{
return readAllChangeLogs (s_aDefaultCallback, aClassLoaderProvider.getClassLoader ());
}
@Nonnull
@ReturnsMutableCopy
public static ICommonsMap readAllChangeLogs (@Nonnull final IChangeLogSerializerCallback aErrorCallback)
{
return readAllChangeLogs (aErrorCallback, (ClassLoader) null);
}
@Nonnull
@ReturnsMutableCopy
public static ICommonsMap readAllChangeLogs (@Nonnull final IChangeLogSerializerCallback aErrorCallback,
@Nullable final ClassLoader aClassLoader)
{
ValueEnforcer.notNull (aErrorCallback, "ErrorCallback");
try
{
final ClassLoader aRealClassLoader = aClassLoader != null ? aClassLoader
: ClassLoaderHelper.getDefaultClassLoader ();
final ICommonsMap ret = new CommonsHashMap<> ();
// Find all change log XML files in the classpath
for (final URL aURL : CollectionHelper.newList (ClassLoaderHelper.getResources (aRealClassLoader,
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 IMicroDocument ret = new MicroDocument ();
final IMicroElement eRoot = ret.appendElement (CChangeLog.CHANGELOG_NAMESPACE_10, ELEMENT_CHANGELOG);
eRoot.setAttribute (XMLConstants.XMLNS_ATTRIBUTE_NS_URI, CXML.XML_NS_PREFIX_XSI, CXML.XML_NS_XSI);
eRoot.setAttribute (CXML.XML_NS_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, DF.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, DF.format (aRelease.getDate ()));
eRelease.setAttribute (ATTR_VERSION, aRelease.getVersion ().getAsString ());
}
}
return ret;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy