I have xml like this:
<horo>
<aries>
<today>
Сегодня вас могут здорово огорчить. Если от расстройства все начнет валится из рук, просто спокойно сядьте и тихонько подождите хорошей новости.
</today>
</aries>
<taurus>
<today>
Сегодня у вас могут возникнуть проблемы на личном фронте. Спасти вас от перспективы оказаться не у дел может сухой, рациональный и в высшей степени объективный подход к проблеме.
</today>
</taurus>
</horo>
And now i learning kotlin whith retrofit. I include libraries for parse xml, and not i cant understand how create object for parsing this xml. I have object:
#Root(name = "horo", strict = false)
open class DailyHoroscope{
#get : Element(name = "aries") var aries : Aries? = null
}
#Root(name = "aries", strict = false)
open class Aries{
#get : Element(name = "today") var today : String? = null
}
but i have error:
rg.simpleframework.xml.core.ConstructorException: Default constructor
can not accept read only #org.simpleframework.xml.Element(data=false,
name=aries, required=true, type=void) on method 'aries' in class
ac.kotlintest.model.
upd
i writed code in java:
#Root(name = "horo", strict = false)
public class DailyHoroscopeJ {
#Element(name = "aries")
public Aries aries;
public Aries getAries() {
return aries;
}
public void setAries(Aries aries) {
this.aries = aries;
}
}
#Root(name = "aries", strict = false)
class Aries{
#Element(name = "today")
public String today;
public String getToday() {
return today;
}
public void setToday(String today) {
this.today = today;
}
}
and it work fine, then i convert to kotlin
#Root(name = "horo", strict = false)
class DailyHoroscope {
#get:Element(name = "aries")
var aries:Aries? = null
}
#Root(name = "aries", strict = false) class Aries {
#get:Element(name = "today")
var today:String? = null
}
but i have same problem((((
The answer of #daementus is almost perfect. If you want to use constructor injection with default parameters, you have to force Kotlin to generate constructor overloads:
data class Section #JvmOverloads constructor(
#field:Element(name = "id")
#param:Element(name = "id")
val id: Long,
#field:Attribute(name = "title", required = false)
#param:Attribute(name = "title", required = false)
val title: String = ""
)
Without it you will get Constructor not matched for class Section.
By default Kotlin generates a constructor with all parameters and a special constructor.
Note: I would prefer to answer in the comments but I don't have enough points.
Indeed, Simple XML Framework has a few problems with Kotlin attributes and it can be a little tricky to get things to work.
To be honest, I am not quite sure what is the problem in your specific case, but I'd guess that the annotation shouldn't be specified for the getter, but rather for the field.
Anyway, I am combining Simple XML and Kotlin data classes this way, and it seems to be working fine :)
data class Section (
#field:Element(name = "id", required = false)
#param:Element(name = "id", required = false)
val id: Long? = null,
#field:Attribute(name = "title", required = false)
#param:Attribute(name = "title", required = false)
val title: String? = null
)
Edit: If you don't want to use data classes (which I highly recommend, but you might have a reason), this should work without the "data" keyword just fine. If you don't wish to create a constructor, just move the attribute declarations directly into the class and get rid of the #param annotation (the #field must stay).
Related
#Override
#Mappings(
{
#Mapping(target = "temperature", source = "pac.temperature"),
#Mapping(target = "containerId", ignore=true),
}
)
TargetABC toDto(Source source);
#AfterMapping
default void inRange(Source source, #MappingTarget TargerABC target) {
var temperature = source.getPac.getTemperature();
var range = source.getRange();
target.setContainerId(
range.calculate(temperature)
);
}
at the moment I have a solution using #AfterMapping, but I want to get rid of this approach in favor of qualifiedByName and do the mapping in the field itself by adding a method with the #Named annotation, is it possible that such a method will take two values? Maybe there is another better solution?
You can define custom method which will accept Sourceas parameter and will make required computations. Call this method from #Mapping using expression= "java(<method_name>)".
#Mapping(target = "containerId",expression = "java(calculateContainerId(source))")
TargetABC toDto(Source source);
default String calculateContainerId(Source source) {
var temperature = source.getPac.getTemperature();
var range = source.getRange();
return range.calculate(temperature);
}
The simplest solution is defining a default method for mapping source to containerId, which would MapStruct recognize and use if you define source as the source used for mapping, as shown below:
#Mapping(target = "temperature", source = "pac.temperature")
#Mapping(target = "containerId", source = "source")
TargerABC toDto(Source source);
default Integer calculateContainerIdFromSource(Source source) {
var temperature = source.getPac().getTemperature();
var range = source.getRange();
return range.calculate(temperature);
}
I hava a class Packet.java(can't modify) in a package.
public class Packet implements java.io.Serializable, Cloneable {
private static final AtomicLong ID_ATOMICLONG = new AtomicLong();
private Long id = ID_ATOMICLONG.incrementAndGet();
}
I use own class LoginPacket.kt (can modify)
class LoginPacket : Packet () {
var id = "" ( this name must be id )
fun parsePacket(input: String): Boolean {
val map = HashMap<String,Any>()
map["id"] = "5d6ff3433354b4d43076419"
var wrapper: BeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(this)
wrapper.isAutoGrowNestedPaths = true
// question is here , I can not set id as String use BeanWrapper, Only can set id as Long
// and also I can replace id's getter and setter method
val pd = wrapper.getPropertyDescriptor("id")
pd.readMethod = LoginPacket::id.getter.javaMethod
pd.writeMethod = LoginPacket::id.setter.javaMethod
wrapper.setPropertyValues(map)
}
}
So what I can do next?
Thanks very much for sharing!
Beanwrapper link
It is not possible to override the type of a field.
What you can do instead depends on what you are trying to do, and which libraries you are using.
I can think of one way that may work, assuming your library does not need an instance nor subclass of Packet.
And that is creating your own class that only implements the interfaces:
class LoginPacket(): java.io.Serializable, Cloneable {
// You may or may not need this.
// Since the original version uses it to generate the ID,
// I think you can skip this part.
companion object {
#JvmStatic
private val ID_ATOMICLONG = AtomicLong()
}
var id : String = ""
fun parsePacket(input: String): Boolean {
val map = HashMap<String,Any>()
map["id"] = "5d6ff3433354b4d43076419"
var wrapper: BeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(this)
wrapper.isAutoGrowNestedPaths = true
val pd = wrapper.getPropertyDescriptor("id")
pd.readMethod = LoginPacket::id.getter.javaMethod
pd.writeMethod = LoginPacket::id.setter.javaMethod
wrapper.setPropertyValues(map)
}
}
It is hard to provide better answers without more context.
I am using mapstruct to map from one DTO to another. I have multiple default methods , but 2 of them with a return value of String and that uses the same class as the input parameter gives me "Ambiguous mapping methods using java Mapstruct" error. I am adding the relevant parts of the code here:
#Mappings({
#Mapping(source = "programInstance", target = "title", qualifiedByName = "title"),
#Mapping(source = "programInstance", target = "seriesName", qualifiedByName = "seriesName"),
#Mapping(source = "programInstance", target = "season", qualifiedByName = "season"),
#Mapping(source = "programInstance", target = "epNumber", qualifiedByName = "epNumber"),
})
DTO1 mapDTOs (DTO2 dto2);
#Named("title")
default String mapTitle(Program programInstance) {
Optional<String> title = Utils.getObject(() -> programInstance.getTitle().getDescriptions().get(0).getValue());
if (title.isPresent())
return title.get();
return null;
}
#Named("seriesName")
default String mapSeriesName(Program programInstance) {
Optional<String> seriesName = Utils.getObject(() -> programInstance.get(0).getProgram().getTitle().getDescriptions().get(0).getValue());
if (seriesName.isPresent())
return seriesName.get();
return null;
}
#Named("season")
default Integer mapSeasonNumber(Program programInstance) {
Optional<Integer> season = Utils.getObject(() -> programInstance.get(0).getSeasonOf().get(0).getOrderNo());
if (season.isPresent())
return season.get();
return null;
}
#Named("epNumber")
default Integer mapEpNumber(Program programInstance) {
Optional<Integer> epNumber = Utils.getObject(() -> programInstance.getEpOf().get(0).getOrderNo());
if (epNumber.isPresent())
return epNumber.get();
return null;
}
The error is
Ambiguous mapping methods found for mapping property "Program
programInstance" to java.lang.String: java.lang.String mapTitle(),
java.lang.String mapSeriesName().
I checked your example.. The problem is that the fields you try to target are of type String.
So:
public class IvpVodOfferStatusDTO {
private String seasonNumber;
private String episodeNumber;
}
MapStruct tries to match this with the signature you provide:
#Named("season")
default Integer mapSeasonNumber(Program programInstance) {
Optional<Integer> season = Utils.getObject(() -> programInstance.get(0).getSeasonOf().get(0).getOrderNo());
if (season.isPresent())
return season.get();
return null;
}
#Named("epNumber")
default Integer mapEpNumber(Program programInstance) {
Optional<Integer> epNumber = Utils.getObject(() -> programInstance.getEpOf().get(0).getOrderNo());
if (epNumber.isPresent())
return epNumber.get();
return null;
}
MapStruct has a predefined order of attempts:
User provided Mapping method
Direct (types source -target are the same)
Mapping method (built-in)
Type conversion
If this all fails MapStruct tries to do a number of 2 step approaches:
mapping method - mapping method
mapping method - type conversion
type conversion - mapping method
At 6. it finds 2 qualifying methods (Program to String). It's probably an error in MapStruct that it selects methods that do not qualify (need to check whether this is intentional) by the #Named. Otherwise, I'll write an issue.
The most easy solution is: adapt the target:
public class IvpVodOfferStatusDTO {
private Integer seasonNumber;
private Integer episodeNumber;
}
What is probably what you intend (I guess).. Otherwise you could change the signature not to return an Integer but a String
I was facing same issue and observed that, there was same method inherited by my mapper class using #Mapper(uses = {BaseMapper.class}) and using extends BaseMapper.
Removing extends solved the problem for me.
So, you can look for method received by custom mapper through multiple ways.
Even if the data types are matching, this could happen if the name given at qualifiedByName is not defined in as a bean instance
Because without a matching #Named qualifier, the injector would not know which bean to bind to which variable
#Mapping( source = "firstName", target = "passenger.firstName", qualifiedByName = "mapFirstName" )
public abstract Passenger mapPassenger( Traveller traveller );
#Named( "mapFirstName" )
String mapFirstName( String firstName)
{
}
https://vladmihalcea.com/how-to-implement-a-custom-string-based-sequence-identifier-generator-with-hibernate/
i tried to this for a field that is not primary key.
Also same solution for here:
How to implement IdentifierGenerator with PREFIX and separate Sequence for each entity
But even it does not go to Java method when i run the program. It saves as null.
And i cant see the log that i put inside my class. There is no log for my class.
I copied from that blog but my code is:
public class StringSequenceIdentifier
implements IdentifierGenerator, Configurable {
public static final String SEQUENCE_PREFIX = "sequence_prefix";
private String sequencePrefix;
private String sequenceCallSyntax;
#Override
public void configure(
Type type, Properties params, ServiceRegistry serviceRegistry)
throws MappingException {
System.out.println("xxx");
final JdbcEnvironment jdbcEnvironment =
serviceRegistry.getService(JdbcEnvironment.class);
final Dialect dialect = jdbcEnvironment.getDialect();
final ConfigurationService configurationService =
serviceRegistry.getService(ConfigurationService.class);
String globalEntityIdentifierPrefix =
configurationService.getSetting( "entity.identifier.prefix", String.class, "SEQ_" );
sequencePrefix = ConfigurationHelper.getString(
SEQUENCE_PREFIX,
params,
globalEntityIdentifierPrefix);
final String sequencePerEntitySuffix = ConfigurationHelper.getString(
SequenceStyleGenerator.CONFIG_SEQUENCE_PER_ENTITY_SUFFIX,
params,
SequenceStyleGenerator.DEF_SEQUENCE_SUFFIX);
final String defaultSequenceName = ConfigurationHelper.getBoolean(
SequenceStyleGenerator.CONFIG_PREFER_SEQUENCE_PER_ENTITY,
params,
false)
? params.getProperty(JPA_ENTITY_NAME) + sequencePerEntitySuffix
: SequenceStyleGenerator.DEF_SEQUENCE_NAME;
sequenceCallSyntax = dialect.getSequenceNextValString(
ConfigurationHelper.getString(
SequenceStyleGenerator.SEQUENCE_PARAM,
params,
defaultSequenceName));
}
#Override
public Serializable generate(SharedSessionContractImplementor session, Object obj) {
System.out.println("xxx");
if (obj instanceof Identifiable) {
Identifiable identifiable = (Identifiable) obj;
Serializable id = identifiable.getId();
if (id != null) {
return id;
}
}
long seqValue = ((Number) Session.class.cast(session)
.createSQLQuery(sequenceCallSyntax)
.uniqueResult()).longValue();
return sequencePrefix + String.format("%011d%s", 0 ,seqValue);
}
}
That is in my domain:
#GenericGenerator(
name = "assigned-sequence",
strategy = "xxxxxx.StringSequenceIdentifier",
parameters = {
#org.hibernate.annotations.Parameter(
name = "sequence_name", value = "hibernate_sequence"),
#org.hibernate.annotations.Parameter(
name = "sequence_prefix", value = "CTC_"),
}
)
#GeneratedValue(generator = "assigned-sequence", strategy = GenerationType.SEQUENCE)
private String referenceCode;
WHAT I WANT IS
I need a unique field, which is not primary. So, i decided that incrementing is best solution because otherwise, i have to check for each created random if it exists in database (i also open suggestions for this).
It will be around 5-6 characters and alphanumeric.
I want to make JPA increment this but it seems i cant do it.
This is very similar to Hibernate JPA Sequence (non-Id) but I don't think it's an exact duplicate. Yet the answers seem to apply and they seem to suggest the following strategies:
Make the field to be generated a reference to an entity with the only purpose that the field now becomes an ID and can get generated by the usual strategies. https://stackoverflow.com/a/536102/66686
Use #PrePersist to fill the field before it gets persisted. https://stackoverflow.com/a/35888326/66686
Make it #Generated and generate the value in the database using a trigger or similar. https://stackoverflow.com/a/283603/66686
I used Android studio's Kotlin plugin to convert my Java class to Kotlin. The thing is it's not Kotlin style still. I want to have Kotlin Data Class instead. But whenever I create it with a primary and secondary constructors it won't work. What would be the correct DATA Class implementation in my case?
class Task {
#SerializedName("_id")
var id: String? = null
#SerializedName("text")
var taskTitle: String? = null
#SerializedName("completed")
var isCompleted: Boolean? = null
constructor(taskTitle: String) {
this.taskTitle = taskTitle
}
constructor(taskTitle: String, completed: Boolean?) {
this.taskTitle = taskTitle
this.isCompleted = completed
}
constructor(id: String, taskTitle: String, isCompleted: Boolean?) {
this.id = id
this.taskTitle = taskTitle
this.isCompleted = isCompleted
}
}
Kotlin introduces default values for parameters in constructor. You can use them to create data class with only one constructor using Kotlin.
It would look like this
data class Task(
#SerializedName("_id") var id: String? = null,
#SerializedName("text") var taskTitle: String? = null,
#SerializedName("completed") var isCompleted: Boolean? = null
)
So you can use your data class with any number of arguments for example:
var task = Task(taskTitle = "title")
var task = Task("id", "title", false)
var task = Task(id = "id", isCompleted = true)
You can even replace argument order
var task = Task(taskTitle = "title", isCompleted = false, id = "id")