mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#47 Support Spring as component model
o Add processor option to set a default component model
This commit is contained in:
parent
76c5e595eb
commit
e6869f20f1
@ -57,6 +57,9 @@ public @interface Mapper {
|
||||
* <li>
|
||||
* {@code cdi}: the generated mapper is an application-scoped CDI bean and
|
||||
* can be retrieved via {@code @Inject}</li>
|
||||
* <li>
|
||||
* {@code spring}: the generated mapper is a Spring bean and
|
||||
* can be retrieved via {@code @Autowired}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return The component model for the generated mapper.
|
||||
|
@ -70,6 +70,21 @@
|
||||
<artifactId>weld-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||
* and/or other contributors as indicated by the @authors tag. See the
|
||||
* copyright.txt file in the distribution for a full listing of all
|
||||
* contributors.
|
||||
*
|
||||
* 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 org.mapstruct.itest.spring;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
public class Source {
|
||||
|
||||
private int foo = 42;
|
||||
|
||||
private Date date = new GregorianCalendar( 1980, 0, 1 ).getTime();
|
||||
|
||||
public int getFoo() {
|
||||
return foo;
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||
* and/or other contributors as indicated by the @authors tag. See the
|
||||
* copyright.txt file in the distribution for a full listing of all
|
||||
* contributors.
|
||||
*
|
||||
* 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 org.mapstruct.itest.spring;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.itest.spring.other.DateMapper;
|
||||
|
||||
@Mapper(componentModel = "spring", uses = DateMapper.class)
|
||||
public interface SourceTargetMapper {
|
||||
|
||||
Target sourceToTarget(Source source);
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||
* and/or other contributors as indicated by the @authors tag. See the
|
||||
* copyright.txt file in the distribution for a full listing of all
|
||||
* contributors.
|
||||
*
|
||||
* 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 org.mapstruct.itest.spring;
|
||||
|
||||
public class Target {
|
||||
|
||||
private Long foo;
|
||||
|
||||
private String date;
|
||||
|
||||
public void setFoo(Long foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
public Long getFoo() {
|
||||
return foo;
|
||||
}
|
||||
|
||||
public String getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||
* and/or other contributors as indicated by the @authors tag. See the
|
||||
* copyright.txt file in the distribution for a full listing of all
|
||||
* contributors.
|
||||
*
|
||||
* 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 org.mapstruct.itest.spring.other;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DateMapper {
|
||||
|
||||
public String asString(Date date) {
|
||||
return date != null ? new SimpleDateFormat( "yyyy" ).format( date ) : null;
|
||||
}
|
||||
|
||||
public Date asDate(String date) {
|
||||
try {
|
||||
return date != null ? new SimpleDateFormat( "yyyy" ).parse( date ) : null;
|
||||
}
|
||||
catch ( ParseException e ) {
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||
* and/or other contributors as indicated by the @authors tag. See the
|
||||
* copyright.txt file in the distribution for a full listing of all
|
||||
* contributors.
|
||||
*
|
||||
* 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 org.mapstruct.itest.spring;
|
||||
|
||||
import org.mapstruct.itest.spring.SpringBasedMapperTest.SpringTestConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test for generation of Spring-based Mapper implementations
|
||||
*
|
||||
* @author Andreas Gudian
|
||||
*/
|
||||
@ContextConfiguration(classes = SpringTestConfig.class )
|
||||
public class SpringBasedMapperTest extends AbstractTestNGSpringContextTests {
|
||||
@Configuration
|
||||
@ComponentScan(basePackageClasses = SpringBasedMapperTest.class)
|
||||
public static class SpringTestConfig {
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private SourceTargetMapper mapper;
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldCreateSpringBasedMapper() {
|
||||
Source source = new Source();
|
||||
|
||||
Target target = mapper.sourceToTarget( source );
|
||||
|
||||
assertThat( target ).isNotNull();
|
||||
assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 42 ) );
|
||||
assertThat( target.getDate() ).isEqualTo( "1980" );
|
||||
}
|
||||
}
|
@ -43,6 +43,7 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<com.jolira.hickory.version>1.0.0</com.jolira.hickory.version>
|
||||
<org.apache.maven.plugins.enforcer.version>1.2</org.apache.maven.plugins.enforcer.version>
|
||||
<org.springframework.version>3.2.3.RELEASE</org.springframework.version>
|
||||
</properties>
|
||||
|
||||
<licenses>
|
||||
@ -142,6 +143,23 @@
|
||||
<version>1.1.5.Final</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>${org.springframework.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>${org.springframework.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${org.springframework.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Project modules -->
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
|
@ -76,7 +76,11 @@ import org.mapstruct.ap.processor.ModelElementProcessor.ProcessorContext;
|
||||
@GeneratePrism(value = Mappings.class, publicAccess = true),
|
||||
@GeneratePrism(value = IterableMapping.class, publicAccess = true)
|
||||
})
|
||||
@SupportedOptions({ MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP, MappingProcessor.UNMAPPED_TARGET_POLICY })
|
||||
@SupportedOptions({
|
||||
MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP,
|
||||
MappingProcessor.UNMAPPED_TARGET_POLICY,
|
||||
MappingProcessor.DEFAULT_COMPONENT_MODEL
|
||||
})
|
||||
public class MappingProcessor extends AbstractProcessor {
|
||||
|
||||
/**
|
||||
@ -86,6 +90,7 @@ public class MappingProcessor extends AbstractProcessor {
|
||||
|
||||
protected static final String SUPPRESS_GENERATOR_TIMESTAMP = "suppressGeneratorTimestamp";
|
||||
protected static final String UNMAPPED_TARGET_POLICY = "unmappedTargetPolicy";
|
||||
protected static final String DEFAULT_COMPONENT_MODEL = "defaultComponentModel";
|
||||
|
||||
private Options options;
|
||||
|
||||
@ -101,7 +106,8 @@ public class MappingProcessor extends AbstractProcessor {
|
||||
|
||||
return new Options(
|
||||
Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ),
|
||||
unmappedTargetPolicy != null ? ReportingPolicy.valueOf( unmappedTargetPolicy ) : null
|
||||
unmappedTargetPolicy != null ? ReportingPolicy.valueOf( unmappedTargetPolicy ) : null,
|
||||
processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL )
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -27,10 +27,13 @@ package org.mapstruct.ap.model;
|
||||
public class Options {
|
||||
private final boolean suppressGeneratorTimestamp;
|
||||
private final ReportingPolicy unmappedTargetPolicy;
|
||||
private final String defaultComponentModel;
|
||||
|
||||
public Options(boolean suppressGeneratorTimestamp, ReportingPolicy unmappedTargetPolicy) {
|
||||
public Options(boolean suppressGeneratorTimestamp, ReportingPolicy unmappedTargetPolicy,
|
||||
String defaultComponentModel) {
|
||||
this.suppressGeneratorTimestamp = suppressGeneratorTimestamp;
|
||||
this.unmappedTargetPolicy = unmappedTargetPolicy;
|
||||
this.defaultComponentModel = defaultComponentModel;
|
||||
}
|
||||
|
||||
public boolean isSuppressGeneratorTimestamp() {
|
||||
@ -40,4 +43,8 @@ public class Options {
|
||||
public ReportingPolicy getUnmappedTargetPolicy() {
|
||||
return unmappedTargetPolicy;
|
||||
}
|
||||
|
||||
public String getDefaultComponentModel() {
|
||||
return defaultComponentModel;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||
* and/or other contributors as indicated by the @authors tag. See the
|
||||
* copyright.txt file in the distribution for a full listing of all
|
||||
* contributors.
|
||||
*
|
||||
* 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 org.mapstruct.ap.model;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.mapstruct.ap.util.Collections;
|
||||
|
||||
/**
|
||||
* Mapper reference which is retrieved via Spring-based dependency injection.
|
||||
* method. Used if "spring" is specified as component model via
|
||||
* {@code Mapper#uses()}.
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
* @author Andreas Gudian
|
||||
*/
|
||||
public class SpringMapperReference extends AbstractModelElement implements MapperReference {
|
||||
|
||||
private Type type;
|
||||
|
||||
public SpringMapperReference(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMapperType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Type> getImportTypes() {
|
||||
return Collections.asSet( type, new Type( "org.springframework.beans.factory.annotation", "Autowired" ) );
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ import org.mapstruct.ap.model.CdiMapperReference;
|
||||
import org.mapstruct.ap.model.Mapper;
|
||||
import org.mapstruct.ap.model.MapperReference;
|
||||
import org.mapstruct.ap.model.Type;
|
||||
import org.mapstruct.ap.util.OptionsHelper;
|
||||
|
||||
/**
|
||||
* A {@link ModelElementProcessor} which converts the given {@link Mapper}
|
||||
@ -40,8 +41,12 @@ public class CdiComponentProcessor implements ModelElementProcessor<Mapper, Mapp
|
||||
@Override
|
||||
public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, Mapper mapper) {
|
||||
String componentModel = MapperPrism.getInstanceOn( mapperTypeElement ).componentModel();
|
||||
String effectiveComponentModel = OptionsHelper.getEffectiveComponentModel(
|
||||
context.getOptions(),
|
||||
componentModel
|
||||
);
|
||||
|
||||
if ( !componentModel.equals( "cdi" ) ) {
|
||||
if ( !"cdi".equalsIgnoreCase( effectiveComponentModel ) ) {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||
* and/or other contributors as indicated by the @authors tag. See the
|
||||
* copyright.txt file in the distribution for a full listing of all
|
||||
* contributors.
|
||||
*
|
||||
* 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 org.mapstruct.ap.processor;
|
||||
|
||||
import java.util.ListIterator;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
|
||||
import org.mapstruct.ap.MapperPrism;
|
||||
import org.mapstruct.ap.model.Annotation;
|
||||
import org.mapstruct.ap.model.Mapper;
|
||||
import org.mapstruct.ap.model.MapperReference;
|
||||
import org.mapstruct.ap.model.SpringMapperReference;
|
||||
import org.mapstruct.ap.model.Type;
|
||||
import org.mapstruct.ap.util.OptionsHelper;
|
||||
|
||||
/**
|
||||
* A {@link ModelElementProcessor} which converts the given {@link Mapper}
|
||||
* object into a Spring bean in case Spring is configured as the
|
||||
* target component model for this mapper.
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
* @author Andreas Gudian
|
||||
*/
|
||||
public class SpringComponentProcessor implements ModelElementProcessor<Mapper, Mapper> {
|
||||
|
||||
@Override
|
||||
public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, Mapper mapper) {
|
||||
String componentModel = MapperPrism.getInstanceOn( mapperTypeElement ).componentModel();
|
||||
String effectiveComponentModel = OptionsHelper.getEffectiveComponentModel(
|
||||
context.getOptions(),
|
||||
componentModel
|
||||
);
|
||||
|
||||
if ( !"spring".equalsIgnoreCase( effectiveComponentModel ) ) {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
mapper.addAnnotation( new Annotation( new Type( "org.springframework.stereotype", "Component" ) ) );
|
||||
|
||||
ListIterator<MapperReference> iterator = mapper.getReferencedMappers().listIterator();
|
||||
while ( iterator.hasNext() ) {
|
||||
MapperReference reference = iterator.next();
|
||||
iterator.remove();
|
||||
iterator.add( new SpringMapperReference( reference.getMapperType() ) );
|
||||
}
|
||||
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 1105;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||
* and/or other contributors as indicated by the @authors tag. See the
|
||||
* copyright.txt file in the distribution for a full listing of all
|
||||
* contributors.
|
||||
*
|
||||
* 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 org.mapstruct.ap.util;
|
||||
|
||||
import org.mapstruct.ap.model.Options;
|
||||
|
||||
/**
|
||||
* Helper class for dealing with {@link Options}.
|
||||
*
|
||||
* @author Andreas Gudian
|
||||
*/
|
||||
public class OptionsHelper {
|
||||
|
||||
private OptionsHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options the options
|
||||
* @param locallyDeclaredComponentModel the locally declared component model
|
||||
*
|
||||
* @return the effective component model to be used
|
||||
*/
|
||||
public static String getEffectiveComponentModel(Options options, String locallyDeclaredComponentModel) {
|
||||
if ( "default".equals( locallyDeclaredComponentModel ) ) {
|
||||
return options.getDefaultComponentModel();
|
||||
}
|
||||
|
||||
return locallyDeclaredComponentModel;
|
||||
}
|
||||
}
|
@ -19,3 +19,4 @@ org.mapstruct.ap.processor.CdiComponentProcessor
|
||||
org.mapstruct.ap.processor.MapperCreationProcessor
|
||||
org.mapstruct.ap.processor.MapperRenderingProcessor
|
||||
org.mapstruct.ap.processor.MethodRetrievalProcessor
|
||||
org.mapstruct.ap.processor.SpringComponentProcessor
|
||||
|
@ -0,0 +1,22 @@
|
||||
<#--
|
||||
|
||||
Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||
and/or other contributors as indicated by the @authors tag. See the
|
||||
copyright.txt file in the distribution for a full listing of all
|
||||
contributors.
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
@Autowired
|
||||
private ${mapperType.name} ${mapperType.name?uncap_first};
|
Loading…
x
Reference in New Issue
Block a user