[MNG-7814] Use location tracking for settings (#1164)

This commit is contained in:
Guillaume Nodet 2023-06-19 12:39:14 +02:00 committed by GitHub
parent e6303aae32
commit 0a8491c329
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 777 additions and 480 deletions

View File

@ -19,12 +19,11 @@
package org.apache.maven.settings;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.io.InputStream;
import java.nio.file.Files;
import org.apache.maven.settings.v4.SettingsXpp3Reader;
import org.apache.maven.api.settings.InputSource;
import org.apache.maven.settings.v4.SettingsXpp3ReaderEx;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -43,8 +42,8 @@ class GlobalSettingsTest {
File globalSettingsFile = new File(basedir, "src/assembly/maven/conf/settings.xml");
assertTrue(globalSettingsFile.isFile(), globalSettingsFile.getAbsolutePath());
try (Reader reader = new InputStreamReader(new FileInputStream(globalSettingsFile), StandardCharsets.UTF_8)) {
new SettingsXpp3Reader().read(reader);
try (InputStream is = Files.newInputStream(globalSettingsFile.toPath())) {
new SettingsXpp3ReaderEx().read(is, true, new InputSource(globalSettingsFile.getAbsolutePath()));
}
}
}

View File

@ -61,6 +61,7 @@ under the License.
</templates>
<params>
<param>packageModelV4=org.apache.maven.api.settings</param>
<param>locationTracking=true</param>
</params>
</configuration>
</execution>

View File

@ -0,0 +1,172 @@
/*
* 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.maven.api.settings;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Class InputLocation.
*/
public class InputLocation implements Serializable, InputLocationTracker {
private final int lineNumber;
private final int columnNumber;
private final InputSource source;
private final Map<Object, InputLocation> locations;
public InputLocation(InputSource source) {
this.lineNumber = -1;
this.columnNumber = -1;
this.source = source;
this.locations = Collections.singletonMap(0, this);
}
public InputLocation(int lineNumber, int columnNumber) {
this(lineNumber, columnNumber, null, null);
}
public InputLocation(int lineNumber, int columnNumber, InputSource source) {
this(lineNumber, columnNumber, source, null);
}
public InputLocation(int lineNumber, int columnNumber, InputSource source, Object selfLocationKey) {
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
this.source = source;
this.locations =
selfLocationKey != null ? Collections.singletonMap(selfLocationKey, this) : Collections.emptyMap();
}
public InputLocation(int lineNumber, int columnNumber, InputSource source, Map<Object, InputLocation> locations) {
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
this.source = source;
this.locations = ImmutableCollections.copy(locations);
}
public int getLineNumber() {
return lineNumber;
}
public int getColumnNumber() {
return columnNumber;
}
public InputSource getSource() {
return source;
}
public InputLocation getLocation(Object key) {
return locations != null ? locations.get(key) : null;
}
public Map<Object, InputLocation> getLocations() {
return locations;
}
/**
* Merges the {@code source} location into the {@code target} location.
*
* @param target the target location
* @param source the source location
* @param sourceDominant the boolean indicating of {@code source} is dominant compared to {@code target}
* @return the merged location
*/
public static InputLocation merge(InputLocation target, InputLocation source, boolean sourceDominant) {
if (source == null) {
return target;
} else if (target == null) {
return source;
}
Map<Object, InputLocation> locations;
Map<Object, InputLocation> sourceLocations = source.locations;
Map<Object, InputLocation> targetLocations = target.locations;
if (sourceLocations == null) {
locations = targetLocations;
} else if (targetLocations == null) {
locations = sourceLocations;
} else {
locations = new LinkedHashMap<>();
locations.putAll(sourceDominant ? targetLocations : sourceLocations);
locations.putAll(sourceDominant ? sourceLocations : targetLocations);
}
return new InputLocation(target.getLineNumber(), target.getColumnNumber(), target.getSource(), locations);
} // -- InputLocation merge( InputLocation, InputLocation, boolean )
/**
* Merges the {@code source} location into the {@code target} location.
* This method is used when the locations refer to lists and also merges the indices.
*
* @param target the target location
* @param source the source location
* @param indices the list of integers for the indices
* @return the merged location
*/
public static InputLocation merge(InputLocation target, InputLocation source, Collection<Integer> indices) {
if (source == null) {
return target;
} else if (target == null) {
return source;
}
Map<Object, InputLocation> locations;
Map<Object, InputLocation> sourceLocations = source.locations;
Map<Object, InputLocation> targetLocations = target.locations;
if (sourceLocations == null) {
locations = targetLocations;
} else if (targetLocations == null) {
locations = sourceLocations;
} else {
locations = new LinkedHashMap<>();
for (int index : indices) {
InputLocation location;
if (index < 0) {
location = sourceLocations.get(~index);
} else {
location = targetLocations.get(index);
}
locations.put(locations.size(), location);
}
}
return new InputLocation(target.getLineNumber(), target.getColumnNumber(), target.getSource(), locations);
} // -- InputLocation merge( InputLocation, InputLocation, java.util.Collection )
/**
* Class StringFormatter.
*
* @version $Revision$ $Date$
*/
public interface StringFormatter {
// -----------/
// - Methods -/
// -----------/
/**
* Method toString.
*/
String toString(InputLocation location);
}
}

View File

@ -0,0 +1,23 @@
/*
* 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.maven.api.settings;
public interface InputLocationTracker {
InputLocation getLocation(Object field);
}

View File

@ -0,0 +1,47 @@
/*
* 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.maven.api.settings;
import java.io.Serializable;
/**
* Class InputSource.
*/
public class InputSource implements Serializable {
private final String location;
public InputSource(String location) {
this.location = location;
}
/**
* Get the path/URL of the settings definition or {@code null} if unknown.
*
* @return the location
*/
public String getLocation() {
return this.location;
}
@Override
public String toString() {
return getLocation();
}
}

View File

@ -266,7 +266,7 @@
</fields>
<codeSegments>
<codeSegment>
<version>1.0.0+</version>
<version>1.0.0/1.2.0</version>
<code>
<![CDATA[
public Boolean getInteractiveMode()
@ -1082,5 +1082,55 @@
</fields>
</class>
<!-- /BuildProfile support -->
<class locationTracker="locations">
<name>InputLocation</name>
<version>2.0.0+</version>
<fields>
<!-- line, column and source fields are auto-generated by Modello -->
</fields>
<codeSegments>
<codeSegment>
<version>2.0.0+</version>
<code>
<![CDATA[
@Override
public String toString() {
return getLineNumber() + " : " + getColumnNumber() + ", " + getSource();
}
]]>
</code>
</codeSegment>
</codeSegments>
</class>
<class sourceTracker="source">
<name>InputSource</name>
<version>2.0.0+</version>
<fields>
<field>
<name>location</name>
<version>2.0.0+</version>
<type>String</type>
<description>
<![CDATA[
The path/URL of the settings definition or {@code null} if unknown.
]]>
</description>
</field>
</fields>
<codeSegments>
<codeSegment>
<version>2.0.0+</version>
<code>
<![CDATA[
@Override
public String toString() {
return getLocation();
}
]]>
</code>
</codeSegment>
</codeSegments>
</class>
</classes>
</model>

View File

@ -28,14 +28,14 @@ import java.io.Writer;
import java.util.Objects;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.model.InputSource;
import org.apache.maven.api.services.xml.SettingsXmlFactory;
import org.apache.maven.api.services.xml.XmlReaderException;
import org.apache.maven.api.services.xml.XmlReaderRequest;
import org.apache.maven.api.services.xml.XmlWriterException;
import org.apache.maven.api.services.xml.XmlWriterRequest;
import org.apache.maven.api.settings.InputSource;
import org.apache.maven.api.settings.Settings;
import org.apache.maven.settings.v4.SettingsXpp3Reader;
import org.apache.maven.settings.v4.SettingsXpp3ReaderEx;
import org.apache.maven.settings.v4.SettingsXpp3Writer;
@Named
@ -52,14 +52,14 @@ public class DefaultSettingsXmlFactory implements SettingsXmlFactory {
try {
InputSource source = null;
if (request.getModelId() != null || request.getLocation() != null) {
source = new InputSource(request.getModelId(), request.getLocation());
source = new InputSource(request.getLocation());
}
SettingsXpp3Reader xml = new SettingsXpp3Reader();
SettingsXpp3ReaderEx xml = new SettingsXpp3ReaderEx();
xml.setAddDefaultEntities(request.isAddDefaultEntities());
if (reader != null) {
return xml.read(reader, request.isStrict());
return xml.read(reader, request.isStrict(), source);
} else {
return xml.read(inputStream, request.isStrict());
return xml.read(inputStream, request.isStrict(), source);
}
} catch (Exception e) {
throw new XmlReaderException("Unable to read settings", e);

View File

@ -24,12 +24,12 @@ import javax.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.maven.api.settings.InputSource;
import org.apache.maven.building.FileSource;
import org.apache.maven.building.Source;
import org.apache.maven.settings.Server;
@ -39,6 +39,7 @@ import org.apache.maven.settings.io.SettingsParseException;
import org.apache.maven.settings.io.SettingsReader;
import org.apache.maven.settings.io.SettingsWriter;
import org.apache.maven.settings.merge.MavenSettingsMerger;
import org.apache.maven.settings.v4.SettingsTransformer;
import org.apache.maven.settings.validation.SettingsValidator;
import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
import org.codehaus.plexus.interpolation.InterpolationException;
@ -158,8 +159,9 @@ public class DefaultSettingsBuilder implements SettingsBuilder {
Settings settings;
try {
Map<String, ?> options = Collections.singletonMap(SettingsReader.IS_STRICT, Boolean.TRUE);
Map<String, Object> options = new HashMap<>();
options.put(SettingsReader.IS_STRICT, Boolean.TRUE);
options.put(InputSource.class.getName(), new InputSource(settingsSource.getLocation()));
try {
settings = settingsReader.read(settingsSource.getInputStream(), options);
} catch (SettingsParseException e) {
@ -211,15 +213,6 @@ public class DefaultSettingsBuilder implements SettingsBuilder {
private Settings interpolate(
Settings settings, SettingsBuildingRequest request, SettingsProblemCollector problems) {
StringWriter writer = new StringWriter(1024 * 4);
try {
settingsWriter.write(writer, null, settings);
} catch (IOException e) {
throw new IllegalStateException("Failed to serialize settings to memory", e);
}
String serializedSettings = writer.toString();
RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
@ -238,37 +231,19 @@ public class DefaultSettingsBuilder implements SettingsBuilder {
e);
}
interpolator.addPostProcessor((expression, value) -> {
if (value != null) {
// we're going to parse this back in as XML so we need to escape XML markup
value = value.toString()
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;");
return value;
}
return null;
});
return new Settings(new SettingsTransformer(value -> {
try {
serializedSettings = interpolator.interpolate(serializedSettings, "settings");
return value != null ? interpolator.interpolate(value) : null;
} catch (InterpolationException e) {
problems.add(
SettingsProblem.Severity.ERROR, "Failed to interpolate settings: " + e.getMessage(), -1, -1, e);
return settings;
SettingsProblem.Severity.WARNING,
"Failed to interpolate settings: " + e.getMessage(),
-1,
-1,
e);
return value;
}
Settings result;
try {
Map<String, ?> options = Collections.singletonMap(SettingsReader.IS_STRICT, Boolean.FALSE);
result = settingsReader.read(new StringReader(serializedSettings), options);
} catch (IOException e) {
problems.add(
SettingsProblem.Severity.ERROR, "Failed to interpolate settings: " + e.getMessage(), -1, -1, e);
return settings;
}
return result;
})
.visit(settings.getDelegate()));
}
}

View File

@ -29,8 +29,9 @@ import java.nio.file.Files;
import java.util.Map;
import java.util.Objects;
import org.apache.maven.api.settings.InputSource;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.v4.SettingsXpp3Reader;
import org.apache.maven.settings.v4.SettingsXpp3ReaderEx;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
@ -47,7 +48,8 @@ public class DefaultSettingsReader implements SettingsReader {
Objects.requireNonNull(input, "input cannot be null");
try (InputStream in = Files.newInputStream(input.toPath())) {
return new Settings(new SettingsXpp3Reader().read(in, isStrict(options)));
InputSource source = new InputSource(input.toString());
return new Settings(new SettingsXpp3ReaderEx().read(in, isStrict(options), source));
} catch (XmlPullParserException e) {
throw new SettingsParseException(e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e);
}
@ -58,7 +60,8 @@ public class DefaultSettingsReader implements SettingsReader {
Objects.requireNonNull(input, "input cannot be null");
try (Reader in = input) {
return new Settings(new SettingsXpp3Reader().read(in, isStrict(options)));
InputSource source = (InputSource) options.get(InputSource.class.getName());
return new Settings(new SettingsXpp3ReaderEx().read(in, isStrict(options), source));
} catch (XmlPullParserException e) {
throw new SettingsParseException(e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e);
}
@ -69,7 +72,8 @@ public class DefaultSettingsReader implements SettingsReader {
Objects.requireNonNull(input, "input cannot be null");
try (InputStream in = input) {
return new Settings(new SettingsXpp3Reader().read(in, isStrict(options)));
InputSource source = (InputSource) options.get(InputSource.class.getName());
return new Settings(new SettingsXpp3ReaderEx().read(in, isStrict(options), source));
} catch (XmlPullParserException e) {
throw new SettingsParseException(e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e);
}

View File

@ -60,6 +60,11 @@ under the License.
<models>
<model>src/main/mdo/settings.mdo</model>
</models>
<params>
<param>packageModelV3=org.apache.maven.settings</param>
<param>packageModelV4=org.apache.maven.api.settings</param>
<param>packageToolV4=org.apache.maven.settings.v4</param>
</params>
</configuration>
<executions>
<execution>
@ -70,18 +75,31 @@ under the License.
<phase>generate-sources</phase>
<configuration>
<templates>
<template>model-v3.vm</template>
<template>merger.vm</template>
<template>transformer.vm</template>
<template>reader.vm</template>
<template>reader-ex.vm</template>
<template>writer.vm</template>
<template>writer-ex.vm</template>
</templates>
<params>
<param>packageModelV3=org.apache.maven.settings</param>
<param>packageModelV4=org.apache.maven.api.settings</param>
<param>packageToolV4=org.apache.maven.settings.v4</param>
<params combine.children="append">
<param>locationTracking=true</param>
</params>
</configuration>
</execution>
<execution>
<id>v3</id>
<goals>
<goal>velocity</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<version>1.2.0</version>
<templates>
<template>model-v3.vm</template>
</templates>
</configuration>
</execution>
</executions>
</plugin>
<plugin>

View File

@ -183,10 +183,18 @@ public class ${class.name}
public void set${cap}(${type} ${field.name}) {
#if ($field.type == "DOM")
if (${field.name} instanceof Xpp3Dom) {
if (!Objects.equals(((Xpp3Dom) ${field.name}).getDom(), getDelegate().${pfx}${cap}())) {
update(getDelegate().with${cap}(((Xpp3Dom) ${field.name}).getDom()));
((Xpp3Dom) ${field.name}).setChildrenTracking(this::replace);
}
} else if (${field.name} == null) {
if (getDelegate().${pfx}${cap}() != null) {
update(getDelegate().with${cap}(null));
}
} else {
throw new IllegalArgumentException("Expected an Xpp3Dom object but received: " + ${field.name});
}
#elseif( $field.type == "java.util.Properties" )
Map<String, String> map = ${field.name}.entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toString()));
@ -194,7 +202,7 @@ public class ${class.name}
update(getDelegate().with${cap}(map));
}
#else
if (!Objects.equals(${field.name}, getDelegate().${pfx}${cap}())) {
if (!Objects.equals(${field.name}, ${pfx}${cap}())) {
#if ( $field.to != "String" && $field.type == "java.util.List" && $field.multiplicity == "*" )
update(getDelegate().with${cap}(
${field.name}.stream().map(c -> c.getDelegate()).collect(Collectors.toList())));

View File

@ -73,6 +73,296 @@ public class ${className} {
this.contentTransformer = contentTransformer;
}
/**
* @see ReaderFactory#newXmlReader
*
* @param reader a reader object.
* @param strict a strict object.
* @throws IOException IOException if any.
* @throws XmlPullParserException XmlPullParserException if
* any.
* @return ${root.name}
*/
public ${root.name} read(Reader reader, boolean strict) throws IOException, XmlPullParserException {
XmlPullParser parser = addDefaultEntities ? new MXParser(EntityReplacementMap.defaultEntityReplacementMap) : new MXParser();
parser.setInput(reader);
return read(parser, strict);
} //-- ${root.name} read(Reader, boolean)
/**
* @see ReaderFactory#newXmlReader
*
* @param reader a reader object.
* @throws IOException IOException if any.
* @throws XmlPullParserException XmlPullParserException if
* any.
* @return ${root.name}
*/
public ${root.name} read(Reader reader) throws IOException, XmlPullParserException {
return read(reader, true);
} //-- ${root.name} read(Reader)
/**
* Method read.
*
* @param in a in object.
* @param strict a strict object.
* @throws IOException IOException if any.
* @throws XmlPullParserException XmlPullParserException if
* any.
* @return ${root.name}
*/
public ${root.name} read(InputStream in, boolean strict) throws IOException, XmlPullParserException {
return read(ReaderFactory.newXmlReader(in), strict);
} //-- ${root.name} read(InputStream, boolean)
/**
* Method read.
*
* @param in a in object.
* @throws IOException IOException if any.
* @throws XmlPullParserException XmlPullParserException if
* any.
* @return ${root.name}
*/
public ${root.name} read(InputStream in) throws IOException, XmlPullParserException {
return read(ReaderFactory.newXmlReader(in));
} //-- ${root.name} read(InputStream)
/**
* Method read.
*
* @param parser a parser object.
* @param strict a strict object.
* @throws IOException IOException if any.
* @throws XmlPullParserException XmlPullParserException if
* any.
* @return ${root.name}
*/
public ${root.name} read(XmlPullParser parser, boolean strict) throws IOException, XmlPullParserException {
$rootUcapName $rootLcapName = null;
int eventType = parser.getEventType();
boolean parsed = false;
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
if (strict && ! "${rootTag}".equals(parser.getName())) {
throw new XmlPullParserException("Expected root element '${rootTag}' but found '" + parser.getName() + "'", parser, null);
} else if (parsed) {
// fallback, already expected a XmlPullParserException due to invalid XML
throw new XmlPullParserException("Duplicated tag: '${rootTag}'", parser, null);
}
$rootLcapName = parse${rootUcapName}(parser, strict);
parsed = true;
}
eventType = parser.next();
}
if (parsed) {
return $rootLcapName;
}
throw new XmlPullParserException("Expected root element '${rootTag}' but found no element at all: invalid XML document", parser, null);
} //-- ${root.name} read(XmlPullParser, boolean)
#foreach ( $class in $model.allClasses )
#if ( $class.name != "InputSource" && $class.name != "InputLocation" )
#set ( $classUcapName = $Helper.capitalise( $class.name ) )
#set ( $classLcapName = $Helper.uncapitalise( $class.name ) )
#set ( $ancestors = $Helper.ancestors( $class ) )
#set ( $allFields = [] )
#foreach ( $cl in $ancestors )
#set ( $dummy = $allFields.addAll( $cl.getFields($version) ) )
#end
private ${classUcapName} parse${classUcapName}(XmlPullParser parser, boolean strict)
throws IOException, XmlPullParserException {
String tagName = parser.getName();
${classUcapName}.Builder ${classLcapName} = ${classUcapName}.newBuilder(true);
for (int i = parser.getAttributeCount() - 1; i >= 0; i--) {
String name = parser.getAttributeName(i);
String value = parser.getAttributeValue(i);
if (name.indexOf(':') >= 0) {
// just ignore attributes with non-default namespace (for example: xmlns:xsi)
#if ( $class == $root )
} else if ("xmlns".equals(name)) {
// ignore xmlns attribute in root class, which is a reserved attribute name
#end
}
#foreach ( $field in $allFields )
#if ( $Helper.xmlFieldMetadata( $field ).attribute )
#set ( $fieldTagName = $Helper.xmlFieldMetadata( $field ).tagName )
#set ( $fieldCapName = $Helper.capitalise( $field.name ) )
else if ("$fieldTagName".equals(name)) {
#if ( $field.type == "String" )
${classLcapName}.${field.name}(interpolatedTrimmed(value, "$fieldTagName"));
#elseif ( $field.type == "boolean" || $field.type == "Boolean" )
${classLcapName}.${field.name}(getBooleanValue(interpolatedTrimmed(value, "$fieldTagName"), "$fieldTagName", parser, ${field.defaultValue}));
#else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end
}
#end
#end
else {
checkUnknownAttribute(parser, name, tagName, strict);
}
}
Set<String> parsed = new HashSet<>();
#foreach ( $field in $allFields )
#if ( $Helper.isFlatItems( $field ) )
List<$field.to> ${field.name} = new ArrayList<>();
#end
#end
while ((strict ? parser.nextTag() : nextTag(parser)) == XmlPullParser.START_TAG) {
String childName = checkDuplicate(parser.getName(), parser, parsed);
switch (childName) {
#set( $ift = "if" )
#foreach ( $field in $allFields )
#if ( ! $Helper.xmlFieldMetadata( $field ).attribute && ! $Helper.xmlFieldMetadata( $field ).transient )
#set ( $fieldTagName = $Helper.xmlFieldMetadata( $field ).tagName )
#if ( ! $fieldTagName )
#set ( $fieldTagName = $field.name )
#end
#if ( $Helper.isFlatItems( $field ) )
#set ( $fieldTagName = $Helper.singular( $fieldTagName ) )
#end
#set ( $fieldCapName = $Helper.capitalise( $field.name ) )
case "${fieldTagName}": {
#if ( $field.type == "String" )
${classLcapName}.${field.name}(interpolatedTrimmed(parser.nextText(), "${fieldTagName}"));
break;
#elseif ( $field.type == "boolean" || $field.type == "Boolean" )
${classLcapName}.${field.name}(getBooleanValue(interpolatedTrimmed(parser.nextText(), "${fieldTagName}"), "${fieldTagName}", parser, ${field.defaultValue}));
break;
#elseif ( $field.type == "int" )
${classLcapName}.${field.name}(getIntegerValue(interpolatedTrimmed(parser.nextText(), "${fieldTagName}"), "${fieldTagName}", parser, strict, ${field.defaultValue}));
break;
#elseif ( $field.type == "DOM" )
${classLcapName}.${field.name}(XmlNodeBuilder.build(parser, true));
break;
#elseif ( $field.type == "java.util.List" && $field.to == "String" && $field.multiplicity == "*" )
List<String> ${field.name} = new ArrayList<>();
while (parser.nextTag() == XmlPullParser.START_TAG) {
if ("${Helper.singular($fieldTagName)}".equals(parser.getName())) {
${field.name}.add(interpolatedTrimmed(parser.nextText(), "${fieldTagName}"));
} else {
checkUnknownElement(parser, strict);
}
}
${classLcapName}.${field.name}(${field.name});
break;
#elseif ( $field.type == "java.util.Properties" && $field.to == "String" && $field.multiplicity == "*" )
Map<String, String> ${field.name} = new LinkedHashMap<>();
while (parser.nextTag() == XmlPullParser.START_TAG) {
String key = parser.getName();
String value = parser.nextText().trim();
${field.name}.put(key, value);
}
${classLcapName}.${field.name}(${field.name});
break;
#elseif ( $field.to && $field.multiplicity == "1" )
${classLcapName}.${field.name}(parse${field.toClass.name}(parser, strict));
break;
#elseif ( $field.to && $field.multiplicity == "*" && $Helper.isFlatItems( $field ) )
${field.name}.add(parse${field.toClass.name}(parser, strict));
break;
#elseif ( $field.to && $field.multiplicity == "*" )
List<$field.to> ${field.name} = new ArrayList<>();
while (parser.nextTag() == XmlPullParser.START_TAG) {
if ("${Helper.singular($fieldTagName)}".equals(parser.getName())) {
${field.name}.add(parse${field.toClass.name}(parser, strict));
} else {
checkUnknownElement(parser, strict);
}
}
${classLcapName}.${field.name}(${field.name});
break;
#else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
break;
#end
}
#set( $ift = "else if" )
#end
#end
default: {
checkUnknownElement(parser, strict);
break;
}
}
}
#foreach ( $field in $allFields )
#if ( $Helper.isFlatItems( $field ) )
${classLcapName}.${field.name}(${field.name});
#end
#end
#if ( $class == $root )
${classLcapName}.modelEncoding(parser.getInputEncoding());
#end
return ${classLcapName}.build();
}
#end
#end
private String checkDuplicate(String tagName, XmlPullParser parser, Set<String> parsed) throws XmlPullParserException {
#set( $aliases = { } )
#set( $flats = { } )
#foreach( $class in $model.allClasses )
#foreach ( $field in $class.getFields($version) )
#set ( $fieldTagName = $Helper.xmlFieldMetadata( $field ).tagName )
#if ( ! $fieldTagName )
#set ( $fieldTagName = $field.name )
#end
#if ( $field.alias )
#set ( $dummy = $aliases.put( $field.alias, $fieldTagName ) )
#end
#if ( $Helper.isFlatItems( $field ) )
#set ( $fieldTagName = $Helper.singular($fieldTagName) )
#set ( $dummy = $flats.put( $fieldTagName, "" ) )
#end
#end
#end
#if ( ! ${aliases.isEmpty()} )
switch (tagName) {
#foreach( $entry in $aliases.entrySet() )
case "${entry.key}":
tagName = "${entry.value}";
#end
}
#end
#if ( ! ${flats.isEmpty()} )
switch (tagName) {
#foreach( $entry in $flats.entrySet() )
case "${entry.key}":
#end
break;
default:
if (!parsed.add(tagName)) {
throw new XmlPullParserException("Duplicated tag: '" + tagName + "'", parser, null);
}
}
#end
return tagName;
}
/**
* Sets the state of the "add default entities" flag.
*
* @param addDefaultEntities a addDefaultEntities object.
*/
public void setAddDefaultEntities(boolean addDefaultEntities) {
this.addDefaultEntities = addDefaultEntities;
} //-- void setAddDefaultEntities(boolean)
public static interface ContentTransformer {
/**
* Interpolate the value read from the xpp3 document
* @param source The source value
* @param fieldName A description of the field being interpolated. The implementation may use this to
* log stuff.
* @return The interpolated value.
*/
String transform(String source, String fieldName);
}
/**
* Method checkFieldWithDuplicate.
*
@ -481,294 +771,4 @@ public class ${className} {
return eventType;
} //-- int nextTag(XmlPullParser)
/**
* @see ReaderFactory#newXmlReader
*
* @param reader a reader object.
* @param strict a strict object.
* @throws IOException IOException if any.
* @throws XmlPullParserException XmlPullParserException if
* any.
* @return ${root.name}
*/
public ${root.name} read(Reader reader, boolean strict) throws IOException, XmlPullParserException {
XmlPullParser parser = addDefaultEntities ? new MXParser(EntityReplacementMap.defaultEntityReplacementMap) : new MXParser();
parser.setInput(reader);
return read(parser, strict);
} //-- ${root.name} read(Reader, boolean)
/**
* @see ReaderFactory#newXmlReader
*
* @param reader a reader object.
* @throws IOException IOException if any.
* @throws XmlPullParserException XmlPullParserException if
* any.
* @return ${root.name}
*/
public ${root.name} read(Reader reader) throws IOException, XmlPullParserException {
return read(reader, true);
} //-- ${root.name} read(Reader)
/**
* Method read.
*
* @param in a in object.
* @param strict a strict object.
* @throws IOException IOException if any.
* @throws XmlPullParserException XmlPullParserException if
* any.
* @return ${root.name}
*/
public ${root.name} read(InputStream in, boolean strict) throws IOException, XmlPullParserException {
return read(ReaderFactory.newXmlReader(in), strict);
} //-- ${root.name} read(InputStream, boolean)
/**
* Method read.
*
* @param in a in object.
* @throws IOException IOException if any.
* @throws XmlPullParserException XmlPullParserException if
* any.
* @return ${root.name}
*/
public ${root.name} read(InputStream in) throws IOException, XmlPullParserException {
return read(ReaderFactory.newXmlReader(in));
} //-- ${root.name} read(InputStream)
/**
* Method read.
*
* @param parser a parser object.
* @param strict a strict object.
* @throws IOException IOException if any.
* @throws XmlPullParserException XmlPullParserException if
* any.
* @return ${root.name}
*/
public ${root.name} read(XmlPullParser parser, boolean strict) throws IOException, XmlPullParserException {
$rootUcapName $rootLcapName = null;
int eventType = parser.getEventType();
boolean parsed = false;
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
if (strict && ! "${rootTag}".equals(parser.getName())) {
throw new XmlPullParserException("Expected root element '${rootTag}' but found '" + parser.getName() + "'", parser, null);
} else if (parsed) {
// fallback, already expected a XmlPullParserException due to invalid XML
throw new XmlPullParserException("Duplicated tag: '${rootTag}'", parser, null);
}
$rootLcapName = parse${rootUcapName}(parser, strict);
parsed = true;
}
eventType = parser.next();
}
if (parsed) {
return $rootLcapName;
}
throw new XmlPullParserException("Expected root element '${rootTag}' but found no element at all: invalid XML document", parser, null);
} //-- ${root.name} read(XmlPullParser, boolean)
#foreach ( $class in $model.allClasses )
#if ( $class.name != "InputSource" && $class.name != "InputLocation" )
#set ( $classUcapName = $Helper.capitalise( $class.name ) )
#set ( $classLcapName = $Helper.uncapitalise( $class.name ) )
#set ( $ancestors = $Helper.ancestors( $class ) )
#set ( $allFields = [] )
#foreach ( $cl in $ancestors )
#set ( $dummy = $allFields.addAll( $cl.getFields($version) ) )
#end
private ${classUcapName} parse${classUcapName}(XmlPullParser parser, boolean strict)
throws IOException, XmlPullParserException {
String tagName = parser.getName();
${classUcapName}.Builder ${classLcapName} = ${classUcapName}.newBuilder(true);
for (int i = parser.getAttributeCount() - 1; i >= 0; i--) {
String name = parser.getAttributeName(i);
String value = parser.getAttributeValue(i);
if (name.indexOf(':') >= 0) {
// just ignore attributes with non-default namespace (for example: xmlns:xsi)
#if ( $class == $root )
} else if ("xmlns".equals(name)) {
// ignore xmlns attribute in root class, which is a reserved attribute name
#end
}
#foreach ( $field in $allFields )
#if ( $Helper.xmlFieldMetadata( $field ).attribute )
#set ( $fieldTagName = $Helper.xmlFieldMetadata( $field ).tagName )
#set ( $fieldCapName = $Helper.capitalise( $field.name ) )
else if ("$fieldTagName".equals(name)) {
#if ( $field.type == "String" )
${classLcapName}.${field.name}(interpolatedTrimmed(value, "$fieldTagName"));
#elseif ( $field.type == "boolean" || $field.type == "Boolean" )
${classLcapName}.${field.name}(getBooleanValue(interpolatedTrimmed(value, "$fieldTagName"), "$fieldTagName", parser, ${field.defaultValue}));
#else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end
}
#end
#end
else {
checkUnknownAttribute(parser, name, tagName, strict);
}
}
Set<String> parsed = new HashSet<>();
#foreach ( $field in $allFields )
#if ( $Helper.isFlatItems( $field ) )
List<$field.to> ${field.name} = new ArrayList<>();
#end
#end
while ((strict ? parser.nextTag() : nextTag(parser)) == XmlPullParser.START_TAG) {
String childName = checkDuplicate(parser.getName(), parser, parsed);
switch (childName) {
#set( $ift = "if" )
#foreach ( $field in $allFields )
#if ( ! $Helper.xmlFieldMetadata( $field ).attribute && ! $Helper.xmlFieldMetadata( $field ).transient )
#set ( $fieldTagName = $Helper.xmlFieldMetadata( $field ).tagName )
#if ( ! $fieldTagName )
#set ( $fieldTagName = $field.name )
#end
#if ( $Helper.isFlatItems( $field ) )
#set ( $fieldTagName = $Helper.singular( $fieldTagName ) )
#end
#set ( $fieldCapName = $Helper.capitalise( $field.name ) )
case "${fieldTagName}": {
#if ( $field.type == "String" )
${classLcapName}.${field.name}(interpolatedTrimmed(parser.nextText(), "${fieldTagName}"));
break;
#elseif ( $field.type == "boolean" || $field.type == "Boolean" )
${classLcapName}.${field.name}(getBooleanValue(interpolatedTrimmed(parser.nextText(), "${fieldTagName}"), "${fieldTagName}", parser, ${field.defaultValue}));
break;
#elseif ( $field.type == "int" )
${classLcapName}.${field.name}(getIntegerValue(interpolatedTrimmed(parser.nextText(), "${fieldTagName}"), "${fieldTagName}", parser, strict, ${field.defaultValue}));
break;
#elseif ( $field.type == "DOM" )
${classLcapName}.${field.name}(XmlNodeBuilder.build(parser, true));
break;
#elseif ( $field.type == "java.util.List" && $field.to == "String" && $field.multiplicity == "*" )
List<String> ${field.name} = new ArrayList<>();
while (parser.nextTag() == XmlPullParser.START_TAG) {
if ("${Helper.singular($fieldTagName)}".equals(parser.getName())) {
${field.name}.add(interpolatedTrimmed(parser.nextText(), "${fieldTagName}"));
} else {
checkUnknownElement(parser, strict);
}
}
${classLcapName}.${field.name}(${field.name});
break;
#elseif ( $field.type == "java.util.Properties" && $field.to == "String" && $field.multiplicity == "*" )
Map<String, String> ${field.name} = new LinkedHashMap<>();
while (parser.nextTag() == XmlPullParser.START_TAG) {
String key = parser.getName();
String value = parser.nextText().trim();
${field.name}.put(key, value);
}
${classLcapName}.${field.name}(${field.name});
break;
#elseif ( $field.to && $field.multiplicity == "1" )
${classLcapName}.${field.name}(parse${field.toClass.name}(parser, strict));
break;
#elseif ( $field.to && $field.multiplicity == "*" && $Helper.isFlatItems( $field ) )
${field.name}.add(parse${field.toClass.name}(parser, strict));
break;
#elseif ( $field.to && $field.multiplicity == "*" )
List<$field.to> ${field.name} = new ArrayList<>();
while (parser.nextTag() == XmlPullParser.START_TAG) {
if ("${Helper.singular($fieldTagName)}".equals(parser.getName())) {
${field.name}.add(parse${field.toClass.name}(parser, strict));
} else {
checkUnknownElement(parser, strict);
}
}
${classLcapName}.${field.name}(${field.name});
break;
#else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
break;
#end
}
#set( $ift = "else if" )
#end
#end
default: {
checkUnknownElement(parser, strict);
break;
}
}
}
#foreach ( $field in $allFields )
#if ( $Helper.isFlatItems( $field ) )
${classLcapName}.${field.name}(${field.name});
#end
#end
#if ( $class == $root )
${classLcapName}.modelEncoding(parser.getInputEncoding());
#end
return ${classLcapName}.build();
}
#end
#end
private String checkDuplicate(String tagName, XmlPullParser parser, Set<String> parsed) throws XmlPullParserException {
#set( $aliases = { } )
#set( $flats = { } )
#foreach( $class in $model.allClasses )
#foreach ( $field in $class.getFields($version) )
#set ( $fieldTagName = $Helper.xmlFieldMetadata( $field ).tagName )
#if ( ! $fieldTagName )
#set ( $fieldTagName = $field.name )
#end
#if ( $field.alias )
#set ( $dummy = $aliases.put( $field.alias, $fieldTagName ) )
#end
#if ( $Helper.isFlatItems( $field ) )
#set ( $fieldTagName = $Helper.singular($fieldTagName) )
#set ( $dummy = $flats.put( $fieldTagName, "" ) )
#end
#end
#end
#if ( ! ${aliases.isEmpty()} )
switch (tagName) {
#foreach( $entry in $aliases.entrySet() )
case "${entry.key}":
tagName = "${entry.value}";
#end
}
#end
#if ( ! ${flats.isEmpty()} )
switch (tagName) {
#foreach( $entry in $flats.entrySet() )
case "${entry.key}":
#end
break;
default:
if (!parsed.add(tagName)) {
throw new XmlPullParserException("Duplicated tag: '" + tagName + "'", parser, null);
}
}
#end
return tagName;
}
/**
* Sets the state of the "add default entities" flag.
*
* @param addDefaultEntities a addDefaultEntities object.
*/
public void setAddDefaultEntities(boolean addDefaultEntities) {
this.addDefaultEntities = addDefaultEntities;
} //-- void setAddDefaultEntities(boolean)
public static interface ContentTransformer {
/**
* Interpolate the value read from the xpp3 document
* @param source The source value
* @param fieldName A description of the field being interpolated. The implementation may use this to
* log stuff.
* @return The interpolated value.
*/
String transform(String source, String fieldName);
}
}