Friday, October 21, 2022

Java enum generic serializer and deserializer

Java enum generic Serializer and Deserializer

 Introduction

Serializing a Java enum is simple using Jackson. It is easy and well known. 
 
If the enum has a single value then Jackson provides @JasonValue which serializes the enum as the value. However de-serializing becomes an issue. This leads you to start to use @JsonSerialize and @JsonDeserialize to control serialization. Soon you run into questions on how to get the Class of your enum passed into the deserializer and how to call the default deserializer hoping not to have to re-invent the wheel.

After using @JsonCreator, and trying to get the default serializer out of the Bean, I decided must be a way to do this and the way shouldn't clutter the enum with too many tags, deserializer subclasses for every enum type, etc.

https://github.com/sirgilligan/EnumerationSerialization/blob/main/README.md

Java implementation for a Generic enum Serializer and Deserializer

Just like the traditional approach the enum's deserializer can be specified as a subclass of a base deserializer.

@JsonSerialize(using = EnumerationSerializer.class)
@JsonDeserialize(using = RGB.Deserializer.class)
@EnumJson(serializeProjection = Projection.NAME)
enum RGB {
	RED,
	GREEN,
	BLUE;

	public static class Deserializer extends EnumerationDeserializer<RGB> {

		private static final long serialVersionUID = 1L;

		public Deserializer() {
			super(RGB.class);
		}
	}
}

This approach allows you to pass the enum Class.  That is not bad, but it is a bit cluttered.

Instead the class of the enum can be specified via annotation!

@JsonSerialize(using = EnumerationSerializer.class)
@JsonDeserialize(using = EnumerationDeserializer.class)
@EnumJson(serializeProjection = Projection.NAME, deserializationClass = RGB.class)
enum RGB {
	RED,
	GREEN,
	BLUE
} 

This new annotation specifies the Class is RGB.class. It also specifies that the default representation on serializtion should be enum.name().  The default representation could be NAME, ORDINAL, VALUE, or ALIAS.

There are two special annotations to identify a value and an alias.

@JsonSerialize(using = EnumerationSerializer.class)
@JsonDeserialize(using = SomeDays.Deserializer.class)
@EnumJson(serializeProjection = Projection.ORDINAL)
enum SomeDays {
	MONDAY("Lunes", "Monday"),
	TUESDAY("Martes", "Tuesday"),
	WEDNESDAY("Miercoles", "Wednesday");

	@EnumJson(serializeProjection = Projection.VALUE)
	final String value;

	@EnumJson(serializeProjection = Projection.ALIAS)
	final String alias;

	SomeDays(String v, String a) {
		this.value = v;
		this.alias = a;
	}

	public static class Deserializer extends EnumerationDeserializer<SomeDays> {
		private static final long serialVersionUID = 1L;
		public Deserializer() {
			super(SomeDays.class);
		}
	}
}

However EnumerationSerializer and EnumerationDeserialize uses reflection to find any member variable named "value" or "alias". This means you don't even have to use the annotation for VALUE or ALIAS if the naming convention is used. Therefore the above example can be de-cluttered and now look like this:


@JsonSerialize(using = EnumerationSerializer.class)
@JsonDeserialize(using = SomeDays.Deserializer.class)
@EnumJson(serializeProjection = Projection.ORDINAL, deserializationClass = SomeDays.class))
enum SomeDays {
	MONDAY("Lunes", "Monday"),
	TUESDAY("Martes", "Tuesday"),
	WEDNESDAY("Miercoles", "Wednesday");

	final String value;
	final String alias;

	SomeDays(String v, String a) {
		this.value = v;
		this.alias = a;
	}
}
  

I really like that! I like that alot.

The annotations can be applied to a class that has members of your enum.

static class SomeStuff {

	//Serialize as the Ordinal
	@EnumJson(serializeProjection = Projection.ORDINAL)
	public SomeDays someDay = SomeDays.MONDAY;

	//Serialize as the Identifier
	@EnumJson(serializeProjection = Projection.NAME)
	public SomeDays nextDay = SomeDays.values()[someDay.ordinal() + 1];

	//Serialize as the Value
	@EnumJson(serializeProjection = Projection.VALUE)
	public SomeDays middleDay = SomeDays.WEDNESDAY;

	//Serialize as the Alias
	@EnumJson(serializeProjection = Projection.ALIAS)
	public SomeDays tuesday = SomeDays.TUESDAY;

	//Serialize as the Ordinal
	@EnumJson(serializeProjection = Projection.ORDINAL)
	public SomeNums aNum = SomeNums.ONE;

	//Serialize as the Identifier
	@EnumJson(serializeProjection = Projection.NAME)
	public SomeNums anotherNum = SomeNums.TWO;

}
 

The Serializer

Use the link to the souce code to see the current implementation.
The code is included here so that you can use it as a reference on how to acheive something using serialization, reflection, or annotations.

https://github.com/sirgilligan/EnumerationSerialization/blob/main/src/main/java/org/example/EnumerationSerializer.java

/*
	EnumerationSerializer serializes an Enum looking for EnumJson annotations.
	If no EnumJson annotation is found the enum is serialized by name.

	If the enum is a member variable of some class then the EnumJson annotation
	at the member variable level is used and takes priority over any Enum class annotation.

	If there are no member variable EnumJson annotation then if there is an Enum class
	annotation it will be used.
 */

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.lang.reflect.Field;
import org.example.EnumJson.Projection;

public class EnumerationSerializer<T extends Enum<T>> extends StdSerializer<Enum<T>>
{

	private static final long serialVersionUID = 1L;

	public EnumerationSerializer()
	{
		this(null);
	}

	protected EnumerationSerializer(Class<Enum<T>> t)
	{
		super(t);
	}


	@SuppressWarnings("unchecked")
	@Override
	public void serialize(Enum<T> value, JsonGenerator gen, SerializerProvider provider) throws IOException
	{

		//Looking for data to write as json
		String dataToWrite = null;

		//Looking for a specific annotation
		//Field annotation has priority over class annotation
		EnumJson enumJsonAnnotation = null;

		//If this value is a field/member variable contained by another object, get the field name.
		String fieldName = gen.getOutputContext().getCurrentName();

		if (null != fieldName) {
			//Does the field have an annotation?

			try {
				enumJsonAnnotation = gen.getOutputContext().getCurrentValue().getClass().getField(fieldName).getAnnotation(EnumJson.class);
			}
			catch (NoSuchFieldException ignored) {
				//ignored
			}

		}

		if (null == enumJsonAnnotation) {
			//There wasn't a field level annotation
			//Is there an annotation on the enum class?

			try {
				enumJsonAnnotation = value.getClass().getAnnotation(EnumJson.class);
			}
			catch (Exception ignored) {
				//ignored
			}
		}

		ObjectMapper mapper = (ObjectMapper) gen.getCodec();

		if (null != enumJsonAnnotation) {
			switch (enumJsonAnnotation.serializeProjection()) {
				case ORDINAL: {
					dataToWrite = mapper.writeValueAsString(value.ordinal());
				}
				break;

				case NAME: {
					dataToWrite = mapper.writeValueAsString(value.name());
				}
				break;

				case ALIAS: {
					Field[] enumFields = value.getClass().getDeclaredFields();
					Field field = findAnnotatedField(enumFields, Projection.ALIAS);
					if (null == field) {
						field = findFieldByName(Projection.ALIAS.name().toLowerCase(), (Class<T>) value.getClass());
					}
					dataToWrite = getData(field, value, mapper);
				}
				break;

				case VALUE: {
					Field[] enumFields = value.getClass().getDeclaredFields();
					Field field = findAnnotatedField(enumFields, Projection.VALUE);
					if (null == field) {
						field = findFieldByName(Projection.VALUE.name().toLowerCase(), (Class<T>) value.getClass());
					}
					dataToWrite = getData(field, value, mapper);
				}
				break;

				default:
					break;
			}
		}
		else {
			//There was not any EnumJson annotation or known field such as value or alias.
			// Write enum as name
			dataToWrite = mapper.writeValueAsString(value.name());
		}

		if (null != dataToWrite) {
			gen.writeRawValue(dataToWrite);
		}
	}

	protected Field findAnnotatedField(Field[] enumFields, EnumJson.Projection projection)
	{
		Field result = null;
		for (Field f : enumFields) {
			EnumJson annie = f.getAnnotation(EnumJson.class);
			if ((null != annie) && (annie.serializeProjection() == projection)) {
				result = f;
				break;
			}
		}

		return result;
	}

	private Field findFieldByName(String fieldName, Class<T> enumClass)
	{
		Field result = null;
		try {
			result = enumClass.getDeclaredField(fieldName);
		}
		catch (NoSuchFieldException ignored) {
			//Ignored
		}
		return result;
	}

	@java.lang.SuppressWarnings("java:S3011")
	protected String getData(Field field, Enum<T> value, ObjectMapper mapper)
	{
		String dataToWrite = null;

		Object foundValue = null;
		if (null != field) {
			try {
				field.setAccessible(true);
				foundValue = field.get(value);
			}
			catch (IllegalAccessException ignored) {
				//ignored
			}
		}

		if (null != foundValue) {
			try {
				dataToWrite = mapper.writeValueAsString(foundValue);
			}
			catch (JsonProcessingException ignored) {
				//ignored
			}
		}

		return dataToWrite;
	}
}

 

The Deserializer


import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
import java.lang.reflect.Field;
import org.example.EnumJson.Projection;

public class EnumerationDeserializer<T extends Enum<T>> extends StdDeserializer<Enum<T>> implements ContextualDeserializer
{

	private static final long serialVersionUID = 1L;
	private transient Class<T> enumClass;
	private transient EnumJson classAnnotation = null;
	private transient EnumJson fieldAnnotation = null;

	protected EnumerationDeserializer()
	{
		this(null);
	}

	protected EnumerationDeserializer(Class<T> vc)
	{
		super(vc);
		this.enumClass = vc;
	}

	@SuppressWarnings("unchecked")
	@Override
	public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException
	{

		//enumClass is not null if the constructor that takes the class as a param
		//is used to create this object.
		if (null != enumClass) {
			classAnnotation = enumClass.getAnnotation(EnumJson.class);
		}

		if (null != property) {
			fieldAnnotation = property.getAnnotation(EnumJson.class);

			if ((null == classAnnotation) && (null == enumClass)) {
				classAnnotation = property.getType().getRawClass().getAnnotation(EnumJson.class);

				if ((null != classAnnotation) && (classAnnotation.deserializationClass().isEnum())) {
					this.enumClass = (Class<T>) classAnnotation.deserializationClass();
				}
			}
		}

		return this;
	}

	/*-------------------------------------------------------------------------------------------
		deserialize will check for four different matches on an enum.
			1) If the json string matches the enum.name
			2) If the enum nas an annotation for an EnumJson Projection = ALIAS
			3) If the enum has an annotation for an EnumJson Projection = VALUE
			4) If the json string matches the enum.ordinal
	 -------------------------------------------------------------------------------------------*/
	@Override
	public Enum<T> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
	{

		Enum<T> result = null;
		final String jsonValue = p.getText();

		boolean caseInsensitive = false;

		if (fieldAnnotation != null) {
			caseInsensitive = fieldAnnotation.deserializeCaseInsensitive();
		}
		else if (classAnnotation != null) {
			caseInsensitive = classAnnotation.deserializeCaseInsensitive();
		}

		//-------------------------------------------------------------------------------------------
		//Check if json matches the Name
		for (final T enumValue : enumClass.getEnumConstants()) {
			if (enumValue.name().equals(jsonValue) || ((caseInsensitive) && enumValue.name().equalsIgnoreCase(jsonValue))) {
				result = enumValue;
			}
		}

		//-------------------------------------------------------------------------------------------
		//Check if the enum has an EnumJson Projection Annotation of ALIAS
		if (null == result) {
			Field[] enumFields = enumClass.getDeclaredFields();
			result = enumByAnnotatedField(enumFields, Projection.ALIAS, jsonValue, caseInsensitive);
		}

		//-------------------------------------------------------------------------------------------
		//Check if the enum has an EnumJson Projection Annotation of VALUE
		if (null == result) {
			Field[] enumFields = enumClass.getDeclaredFields();
			result = enumByAnnotatedField(enumFields, Projection.VALUE, jsonValue, caseInsensitive);
		}

		//-------------------------------------------------------------------------------------------
		//Check if json matches the Ordinal
		if (null == result) {
			for (final T enumValue : enumClass.getEnumConstants()) {
				if (Integer.toString(enumValue.ordinal()).equals(jsonValue)) {
					result = enumValue;
				}
			}
		}

		return result;
	}

	@java.lang.SuppressWarnings("java:S3011")
	protected Enum<T> enumByAnnotatedField(Field[] enumFields, EnumJson.Projection projection, String jsonValue, boolean caseInsensitive)
	{

		Enum<T> result = null;

		Field valueField = null;
		for (Field f : enumFields) {
			EnumJson annie = f.getAnnotation(EnumJson.class);
			if ((null != annie) && (annie.serializeProjection() == projection)) {
				valueField = f;
				break;
			}
		}

		if (null != valueField) {
			//The enum has a EnumJson Projection that matches
			valueField.setAccessible(true);
			try {
				for (final T enumValue : enumClass.getEnumConstants()) {

					//Get the projected value from the enum.
					Object projectedValue = valueField.get(enumValue);

					if ((null != projectedValue) &&
					    ((projectedValue.toString().equals(jsonValue)) ||
					     ((caseInsensitive) && projectedValue.toString().equalsIgnoreCase(jsonValue)))) {
						result = enumValue;
					}
				}
			}
			catch (IllegalAccessException ignored) {
				//ignored
			}
		}
		else {
			//Look for a field by named value or alias.
			if (projection == Projection.VALUE) {
				result = getFromKnownField(Projection.VALUE.name().toLowerCase(), jsonValue, caseInsensitive);
			}
			else if (projection == Projection.ALIAS) {
				result = getFromKnownField(Projection.ALIAS.name().toLowerCase(), jsonValue, caseInsensitive);
			}

		}

		return result;
	}

	protected Enum<T> getFromKnownField(String fieldName, String jsonValue, boolean caseInsensitive)
	{
		Enum<T> result = null;

		try {
			Field knownField = enumClass.getDeclaredField(fieldName);
			for (final T enumValue : enumClass.getEnumConstants()) {
				Object value = knownField.get(enumValue);

				if ((null != value) &&
				    ((value.toString().equals(jsonValue)) || ((caseInsensitive) && value.toString().equalsIgnoreCase(jsonValue)))) {
					result = enumValue;
					break;
				}
			}
		}
		catch (NoSuchFieldException | IllegalAccessException ignored) {
			//ignored
		}

		return result;
	}
}
 

The Annotations 

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@EnumAnnotation
public @interface EnumJson
{

	enum Projection
	{
		ALIAS,   //Enum has a property for an alias or alternate name. E.g. Monday
		NAME,    //Enum.name. E.g. MONDAY
		ORDINAL, //Enum.ordinal. E.g. 0, 1, 3, 4, etc.
		VALUE    //Enum has property for some type of value. E.g. LUNES
	}

	Projection serializeProjection() default Projection.VALUE;

	boolean deserializeCaseInsensitive() default false;

	Class<?> deserializationClass() default Void.class;
}

 

 

Thursday, August 11, 2022

Spring IOC - @Conditional

 Prerequisite


To create a new Spring Boot Application project in Intellij go to File -> New -> Project…



Select “Spring Initializer” and give your project a name.

I prefer “Gradle”, so I selected Gradle instead of Maven. Select your JDK, etc.

Go to the next page and under “Web” select Spring Web.


For the example that follows I named it “Springy” and set the group to “com.slinky.springy”.

Introduction


This “example” will show how to use @Conditional in an @Configuration.

You should be familiar with the basic terminology of Spring Boot dependency injection. I recommend reading “Baeldung : Spring Dependency Injection”.

This example will use dependency injection to choose a specific implementation based on some logical condition. The condition I have chosen is simple: the value of an environment variable.

I use Intelli, so edit the configuration by adding “Environment variables:”. If you don’t see “Environment variables:” choose the “Modify options ⌄” to add the field to the dialog.




Create the Following Java Classes


StringStuff.java

package com.slinky.springy;

public interface StringStuff
{
 String getString();
}


DevStringStuff.java

package com.slinky.springy;

import org.springframework.context.annotation.Conditional;

@Conditional(DevCondition.class)
public class DevStringStuff implements StringStuff
{
 @Override
 public String getString()
 {
   return "dev";
 }
}

StageStringStuff.java


package com.slinky.springy;

import org.springframework.context.annotation.Conditional;

@Conditional(StageCondition.class)
public class StageStringStuff implements StringStuff
{
 @Override
 public String getString()
 {
   return "stage";
 }
}


DevCondtion.java


package com.slinky.springy;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class DevCondition implements Condition
{
 @Override
 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
 {
   String environment = context.getEnvironment().getProperty("env");
   return environment != null && environment.contains("dev");
 }
}


StageCondition.java


package com.slinky.springy;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class StageCondition implements Condition
{
 @Override
 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
 {
   String environment = context.getEnvironment().getProperty("env");
   return environment != null && environment.contains("stage");
 }
}


Configurator.java


package com.slinky.springy;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.slinky.springy")
public class Configurator
{
 @Bean
 @Conditional(DevCondition.class)
 public StringStuff getDevStringStuff() {
   return new DevStringStuff();
 }

 @Bean
 @Conditional(StageCondition.class)
 public StringStuff getStageStringStuff() {
   return new StageStringStuff();
 }

}


SpringyApplication.java


package com.slinky.springy;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication(scanBasePackages = {"com.slinky.springy"})
@RestController
public class SpringyApplication
{

 @Autowired(required = false)
 private StringStuff stuffService;

 public static void main(String[] args)
 {
   SpringApplication.run(SpringyApplication.class, args);
 }

 @GetMapping("/env")
 public String env() {
   if (null != stuffService) {
     return stuffService.getString();
   }
   return "env error";
 }

}


Conclusion


The preceding example is so that you can create a working solution that uses @Condition. I am not going to explain why it works or comment on Spring IOC. This is to help you see a working version instead of trying to decipher the documentation and tutorials you may find on the Web.

Run the Java project and open a browser window to this location:

http://localhost:8080/env

The use of an environment variable is just a simple way to create a condition. This may not be the best way to inject behavior based on a runtime environment variable.


Saturday, June 11, 2022

Scroll View - Interface Builder

 Here are the steps to make a Scroll View that has an embedded UIView that specifies the height of the contents of the scrollable area.

Create a new app with a View Controller.

Place a Scroll View inside the View Controller.




Constrain the position of the Scroll View. This example will position the Scroll View to use most of the available area. Just use the sizing handles and make the Scroll View fill the parent view. You can choose any size you want. Constrain the scroll view to the Safe Area.


Next place a UIView inside the Scroll View. This example will use a "Container View".







Use the sizing handles to make the Container View the same size as the Scroll View.






Constrain the Container View to the Scroll View's "Content Layout Guide". Constrain leading, top, trailing, and bottom and set all of their values to"0".








Now constrain the Container View to the Scroll View's "Frame Layout Guide". Constrain to "Equal Widths" and set the multiplier value of the constraint to "1".




This example will show vertical scrolling. Therefore set the height of the Container View to something larger than the Scroll View.


This example sets the height to "1200".



Before testing this, place a label somewhere near the bottom of the Container View.



Now run it in the iPhone Simulator. This example uses an iPhone 8.

Drag in the simulator to see the label.




And that's it!

Monday, May 30, 2022

StackView - Constraining Contained Views

 These are the steps to layout a StackView's contained views by constraining the height of the contained views.


Step 1: Add Vertical Stack View


Step 2: Constrain the Stack View


Step 3: Add UIView to the Stack View


Step 4: Duplicate the UIView so that there are three UIViews


Step 5: Set the background color of each UIView to a unique color.


Step 6: Constrain the top UIView to the bottom UIView in the stack. Set to "Equal Heights"


Step 7: Constrain the middle UIView to the bottom UIView in the stack. Set to "Equal Heights"


Step 8: Change the middle UIView's constraint multiplier to 1.0



Step 9: Change the top UIView's constraint multiplier to 0.5

Sunday, May 29, 2022

UIViewExtended - StackView and FillProportionally

 Hav you have ever put a UIView in a StackView and wondered why "Fill Proportionally" wasn't working when you set the UIView's "Intrinsic Size"?

It's because the UIView doesn't let the intrinsic value be set, and therefore it can't be read.

So, I am thinking, there must be a way to add a property to the UIView via a subclass that will get the job done.

The approach is to use @IBInspectable.

Here is the source:


//

//  UIViewExtended.swift

//

//  Created by Geoffrey Slinker on 5/28/22.

//  Copyright © 2022 Geoffrey Slinker. All rights reserved.

//


import Foundation

import UIKit


class UIViewExtended : UIView {

    

    enum Keys: String {

      case proportionalSize = "ProportionalSize"

    }

    

    var proportionalSize : CGSize

    

    

    override init(frame: CGRect) {

        proportionalSize = CGSize(width:1.0, height:1.0)

        super.init(frame:frame)

    }

    

    required init?(coder aDecoder: NSCoder) {

        let tempSize : CGSize? = aDecoder.decodeCGSize(forKey: Keys.proportionalSize.rawValue) as? CGSize

        if tempSize != nil {

            proportionalSize = tempSize!

        }

        else {

            proportionalSize = CGSize(width:1.0, height:1.0)

        }

        super.init(coder: aDecoder)

    }

    

    override func encode(with coder: NSCoder) {

        coder.encode(self.proportionalSize, forKey: Keys.proportionalSize.rawValue)

        super.encode(with: coder)

    }

    

    @IBInspectable var intrinsicSize: CGSize {

        get {

            return proportionalSize

        }

        set {

            proportionalSize = newValue

        }

    }

    

    override open var intrinsicContentSize : CGSize {

        get {

            return proportionalSize

        }

    }

    

}


Just set the intrinsic size in IB (Interface Builder). If you want to have the simulated view in IB look the same, keep the custom intrinsic size the same as the intrinsic size placeholder values. Yes, you have to manually keep the values the same, but at least you can do it all in IB.




Set the values here:







If you want to UI to simulate the proportions then keep the custom value the same as the Intrinsic Size Placeholder value.




Here is the unit test. The unit test also gets the XML so that it can be examined in the debugger.


func testUIViewExtended() throws {

        let view : UIViewExtended = UIViewExtended.init(frame: CGRect(x: 0,y: 0, width: 100, height: 50))

        

        view.intrinsicSize = CGSize(width: 5,height: 5)

        

        

        //Look at the xml in the debugger

        let archiver = NSKeyedArchiver(requiringSecureCoding: false)

        archiver.outputFormat = .xml

        archiver.encodeRootObject(view)

        let someData = archiver.encodedData

        let stringData = String(decoding: someData, as: UTF8.self)


        

        let codedData = try! NSKeyedArchiver.archivedData(withRootObject: view,

                                                            requiringSecureCoding: false)

        

        

        let result = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(codedData) as?

            UIViewExtended

        

        XCTAssert(result?.frame.width == 100)

        XCTAssert(result?.frame.height == 50)


        XCTAssert(result?.intrinsicSize.width == 5)

        XCTAssert(result?.intrinsicSize.height == 5)


    }


See also:


StackView - Constraining Contained Views

Wednesday, January 26, 2022

AntPathMatcher replace with PathPatternParser

AntPathMatcher setCaseSensitive not working?

Did you update the version of Spring Boot? Did your URI case insensitivity stop working?

Did you have something like this:

@Configuration

public class WebConfig implements WebMvcConfigurer

{

    @Override

    public void configurePathMatch(PathMatchConfigurer configurer)

    {

        AntPathMatcher matcher = new AntPathMatcher();

        matcher.setCaseSensitive(false);

        configurer.setPathMatcher(matcher);

    }


Change it to something like this:


@Configuration

public class WebConfig implements WebMvcConfigurer

{

    @Override

    public void configurePathMatch(PathMatchConfigurer configurer)

    {

        PathPatternParser patternParser = new PathPatternParser();

        patternParser.setCaseSensitive(false);

        configurer.setPatternParser(patternParser);

    }

If you want to continue to use AntPathMatcher, add this to your application.properties file:

spring.mvc.pathmatch.matching-strategy=ant-path-matcher


Wednesday, January 05, 2022

Boxing an Array in Swift

Swift Arrays

If you are reading this you probably have the need to work with an array returned from a function or a copy of an array. I have been porting a complex data transfer object (DTO) from Java to Swift. The Java implementation of this DTO has many violations the Law of Demeter.

The Swift implementation needs to resemble the Java implementation so that changes to one code base will be easier to make in the other code base. This requirement lead me to creating a Boxed Array which I named "List".  Before critiquing the name of the class or the method names, this is to make the port from Java as simple as possible FOR ME. You can make List implement the methods of a Swift Array if you want, or make Protocols for your own "collection classes".

This example of Boxing / Wrapping a Swift Array is as simple as possible. This includes the ability to serialize the List class to be the same as an Array when serialized to JSON.



//Geoffrey Slinker - Just keep my name here please.
import Foundation


public class List<T : Equatable & Codable> : Sequence, Codable
{
    var container : [T] = []
    
    public init() {
        
    }
    
    //------------------------------------------------------------------------
    //JSON Serialization
    required public init(from decoder:Decoder) throws {
        try container = [T](from: decoder)
    }
    
    public func encode(to encoder: Encoder) throws {
        try container.encode(to: encoder)
    }
    //------------------------------------------------------------------------
    
    public func makeIterator() -> ListIterator<T> {
        return ListIterator(self)
    }
    
    public func append(_ newElement:T) {
        container.append(newElement)
    }
    
    public func get(_ at : Int) -> T? {
        return container[at]
    }
    
    public func remove(at : Int) {
        container.remove(at: at)
    }
    
    public func remove(_ element:T) {
        if let index = container.firstIndex(where: {$0 == element}) {
            container.remove(at: index)
        }
    }
    
    public func clear() {
        container.removeAll()
    }
    
    public var count : Int {
        get {
            return container.count
        }
    }
}

public struct ListIterator<T : Equatable & Codable> : IteratorProtocol
{
    let list : List<T>
    var currentPosition = 0
    
    init(_ list: List<T>) {
        self.list = list
    }
    
    public mutating func next() -> T? {
        if(currentPosition < list.count) {
            let result = list.get(currentPosition)
            currentPosition += 1
            return result
        }
        return nil
    }
    
}

Test Code

import XCTest

class ListTests: XCTestCase {

    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }


    func testJson() throws {
        let myArray = [0, 1, 2, 3]
        
        let arrayData = try! JSONSerialization.data(withJSONObject: myArray, options: [])
        let arrayJson = String(data: arrayData, encoding: String.Encoding.utf8)
        
        
        let myList : List<Int> = List<Int>()
        myList.append(0)
        myList.append(1)
        myList.append(2)
        myList.append(3)
        

        let listData = try! JSONEncoder().encode(myList)
        let listJson = String(data: listData, encoding: String.Encoding.utf8)
        
        XCTAssertEqual(arrayJson, listJson)
        

        let anotherList : List<Int>? = try JSONDecoder().decode(List<Int>.self, from: listData)
        XCTAssert(anotherList!.count == 4)
        XCTAssertEqual(anotherList?.get(0), 0)
        XCTAssertEqual(anotherList?.get(1), 1)
        XCTAssertEqual(anotherList?.get(2), 2)
        XCTAssertEqual(anotherList?.get(3), 3)
        
        let yetAnotherList : List<Int>? = try JSONDecoder().decode(List<Int>.self, from: arrayData)
        XCTAssert(yetAnotherList!.count == 4)
        XCTAssertEqual(yetAnotherList?.get(0), 0)
        XCTAssertEqual(yetAnotherList?.get(1), 1)
        XCTAssertEqual(yetAnotherList?.get(2), 2)
        XCTAssertEqual(yetAnotherList?.get(3), 3)
    }

}