equals メソッドと hashCode メソッドが override されたかを調べる。

あるクラスが、java.lang.Objectequals メソッドと hashCode メソッドを
override して定義しているかを調べる。

equals を override しているかを調べるメソッド

public static boolean isEqualsOverride(Class<?> cls){
   try{
      Method equalsMethod = cls.getMethod("equals", Object.class);
      Class<?> declaringClass = equalsMethod.getDeclaringClass();
      if (declaringClass.equals(Object.class)){
         return false;
      }
      try{
         declaringClass.getSuperclass().getMethod("equals", Object.class);
         return true;
      }catch(NoSuchMethodException e){
         for(Class<?> iface : declaringClass.getInterfaces()){
            try{
               iface.getMethod("equals", Object.class);
               return true;
            }catch(NoSuchMethodException e2){
            }
         }
         return false;
      }
   }catch(NoSuchMethodException | SecurityException e){
      throw new RuntimeException(e);
   }
}

hashCode を override しているかを調べるメソッド

public static boolean isHashCodeOverride(Class<?> cls){
   try{
      Method hashCodeMethod = cls.getMethod("hashCode");
      Class<?> declaringClass = hashCodeMethod.getDeclaringClass();
      if (declaringClass.equals(Object.class)){
         return false;
      }
      try{
         declaringClass.getSuperclass().getMethod("hashCode");
         return true;
      }catch(NoSuchMethodException e){
         for(Class<?> iface : declaringClass.getInterfaces()){
            try{
               iface.getMethod("hashCode");
               return true;
            }catch(NoSuchMethodException e2){
            }
         }
         return false;
      }
   }catch(NoSuchMethodException | SecurityException e){
      throw new RuntimeException(e);
   }
}

それぞれ、override していれば、true を返す。
さらに、、
equals と hashCode 両方ともに override しているかを調べるメソッド

public static boolean isEqualsHashCodeOverride(Class<?> cls){
   try{
      Method equalsMethod = cls.getMethod("equals", Object.class);
      Class<?> declarEqualsClass = equalsMethod.getDeclaringClass();
      if (declarEqualsClass.equals(Object.class)){
         return false;
      }
      Method hashCodeMethod = cls.getMethod("hashCode");
      Class<?> declarHashCodeClass = hashCodeMethod.getDeclaringClass();
      if (declarHashCodeClass.equals(Object.class)){
         return false;
      }
      try{
         declarEqualsClass.getSuperclass().getMethod("equals", Object.class);
         declarHashCodeClass.getSuperclass().getMethod("hashCode");
         return true;
      }catch(NoSuchMethodException e){
         boolean b = false;
         for(Class<?> iface : declarEqualsClass.getInterfaces()){
            try{
               iface.getMethod("equals", Object.class);
               b = true;
            }catch(NoSuchMethodException e2){
            }
            if (b) break;
         }
         if (b){
            for(Class<?> iface : declarHashCodeClass.getInterfaces()){
               try{
                  iface.getMethod("hashCode");
                  return true;
               }catch(NoSuchMethodException e2){
               }
            }
         }
         return false;
      }
   }catch(NoSuchMethodException | SecurityException e){
      throw new RuntimeException(e);
   }
}

注意:int.class など、Primitive のクラスでは、使えない。当然、NoSuchMethodException になる。

gradle の copy タスク活用

MyBatis を使用した開発プロジェクトで、gradle を使用した時にマッパーXMLを配置するのに、
不都合なことがある。

Java の interface クラスを置いたJavaソースと同じ場所に、XMLを置いてビルドした時、
classpathXMLも配置して欲しいのである。
例えば、、

src--main-+--java---aa/bbb/ccc
          |
          |
          +--resources

というsrc (ソース)の下がこのようにディレクトリ、aa.bbb.ccc パッケージがあって、
aa.bbb.ccc パッケージに、UserMapper.java

package aa.bbb.ccc
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper{
   @Select("SELECT COUNT(id) FROM USERS WHERE DELETE_FLG = 0")
   public long countUsers();
}

があったとする。更に、長いSQLを書く為に、UserMapper.xml を、
resources の下に、aa.bbb.ccc パッケージのとおりに
ディレクトリ階層を作って配置するのは、手間もかかり
誤ったディレクトリを作って配置したら気がつくのに時間かかって苦労する。
Javaソースと同じ、src--main-+--java---aa/bbb/ccc に UserMapper.xml を配置してビルドした時に、
build/resources の下に配置して欲しいのだ。


sourceSets で、デフォルトの src/main/resources だけでなく追加する方法、、、

sourceSets.main.resources.srcDirs = [ "src/main/resources", "src/main/java" ]

これで、src/main/java/aa/bbb/ccc/UserMapper.xml は、
ビルドした時に、build/resources の下に、
build/resources/aa/bbb/ccc/UserMapper.xml とコピーしてくれるのだが、
 欠点がある!!
 aa/bbb/ccc 以外の 他のパッケージ構成も build/resources の下に作成されてしまう。


gradle のコピータスクを用意する方法、、、
コピーを定義して、build finalizedBy として最後にコピーを実行させる

task copyMapperXML(type: Copy) {
   from 'src/main/java/aa/bbb/ccc'
   into 'build/resources/aa/bbb/ccc'
   include '*.xml'
}
build {
   finalizedBy copyMapperXML
}

マッパーXMLが1つのパッケージだけでなく、複数のパッケージに存在するなら、
その分、コピータスク、finalizedBy を列挙すれば良いだろう。

build {
   finalizedBy finalizedBy( 'copyMapperXML', 'copyMapperXML2')
}

コピー対象が複数パターンなら、

     include('*.xml', '*.properties')

SpringBoot HikariCP の コンフィグレーション

以下を書いたが、、
oboe2uran.hatenablog.com

HikariCP を意識すると、、

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Data;
/**
 * HikariDataSource として読み込む
 */
@Component
@ConfigurationProperties(prefix = "spring")
@Data
public class DatasourcePropertiesReader{
   private HikariDataSource datasource;
}

これを使用する形で、、

@Configuration
@MapperScan(basePackages = { "aaa.bbb.ccc" })
@Data
@Slf4j
public class DataBaseConfiguration{
   @Autowired 
   private DatasourcePropertiesReader datasourcePropertiesReader;

   @Bean
   public DatasourcePropertiesReader getDataSourcePropertiesReader() {
      return datasourcePropertiesReader;
   }

   @Bean
   public SqlSessionFactory getSqlSessionFactory() {
      try{
         SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
         HikariDataSource dataSource = getDataSourcePropertiesReader().getDatasource();
         bean.setDataSource(dataSource);
         SqlSessionFactory factory = bean.getObject();
         factory.getConfiguration().setMapUnderscoreToCamelCase(true);
         return factory;
      }catch(Exception e){
         log.error(e.getMessage(), e);
      }
      return null;
   }
}

SpringBoot mybatis の コンフィグレーション

今更、ではあるがメモ。
SpringBoot Ver 2.3.1
mybatis-spring-boot-starter は、Ver 2.1.3

コードの読みやすさの為に、lombok を使うことにして、
build.gradle の設定は、、

dependencies {

  implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
  implementation 'org.springframework.boot:spring-boot-starter-jdbc'
  implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3'
  implementation 'mysql:mysql-connector-java:8.0.21'
  implementation 'com.zaxxer:HikariCP:3.4.5'
  compileOnly('org.projectlombok:lombok')
  runtimeOnly 'mysql:mysql-connector-java'

}

DB接続プールは、HikariCP である。
application.yml の記述

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: "jdbc:mysql://127.0.0.1:3306/sampleDB?serverTimezone=JST"
    username: root
    password: pass
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 4
      maximum-pool-size: 10

SqlSessionFactory を生成する為に以下を用意しておけば良いだろう。
@MapperScan で、SQLMap マッパー interface を置くベースパッケージを宣言、
setMapUnderscoreToCamelCase(true) で、SnakeケースからCAMELケースへの自動変換を設定

import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import lombok.extern.slf4j.Slf4j;
/**
 * DataBaseConfiguration
 */
@Configuration
@MapperScan(basePackages = { "aaa.bbb.ccc" })
@Slf4j
public class DataBaseConfiguration {
   @Autowired
   private DataSource dataSource;

   @Bean
   public SqlSessionFactory getSqlSessionFactory() {
      try{
         SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
         bean.setDataSource(dataSource);
         SqlSessionFactory factory = bean.getObject();
         factory.getConfiguration().setMapUnderscoreToCamelCase(true);
         return factory;
      }catch(Exception e){
         log.error(e.getMessage(), e);
      }
      return null;
   }
}

あとは。。

@Autowired
private SqlSessionFactory sqlSessionFactory;
SqlSession session = sqlSessionFactory.openSession();

重複存在すれば true を返すCollectors

以前、リストの重複チェックとして、
oboe2uran.hatenablog.com
を書きました。

ここに書いたのは、全てユニークだったら true であり Boolean#logicalAnd を利用したものでした。

では、重複だったら true を返すCollectors にしたい場合は、、
logicalXor を使って

public static<T> Collector<T,?,Boolean> duplicatedElements(){
    Set<T> set = new HashSet<>();
    return Collectors.reducing(true, set::add, Boolean::logicalXor);
}

となります。
でも、全て同じ要素であれば、false になるので、いまいち使えない。

カナ・かな正規表現

昔、書いておいたが、
カナ正規表現 - Oboe吹きプログラマの黙示録
Unicode で書く正規表現

全角カナ: ^[ァ-ー]+$
全角かな: ^[ぁ-ゟー]+$
全角かな(長音含めない): ^[ぁ-ゟ]+$
半角カナ: ^[。-゚]+$

全角カナ: ^[\u30A1-\u30FE]+$
全角かな: ^[\u3041-\u309F\u30FC]+$
全角かな(長音含めない): ^[\u3041-\u309F]+$
平仮名として認識する文字に、長音(ー)を含めるのかどうか?
この論争には決着つきそうもないけど、、
Pythonregex ライブラリによる実行では、、
pypi.org
カタカナは、、、

import regex

p = regex.compile(r'\p{Block=katakana}+')
print(p.fullmatch('アーン'))

の結果は、

<regex.Match object; span=(0, 3), match='アーン'>

ひらがな、では、、、

p = regex.compile(r'\p{Block=Hiragana}+')
print(p.fullmatch('あーん'))

の結果は、

None

ついでなので、、、Python regex では、
漢字にマッチ

p = regex.compile(r'\p{Script=Han}+')
print(p.fullmatch('漢字'))

JavaScript ES2018 で追加された文字も
JavaScript ECMAScript ですね、)

p = regex.compile(r'\p{Script_Extensions=Han}+')
print(p.fullmatch('〆㊊㈴㈲㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃'))

〆 や、(株) なんて、よく使いそうな文字である。