2018年5月31日 星期四

[Java] 使用另一個列表進行排序

class Foo {
    private String id;
    public Foo(String id) { this.id = id; }
    public String getId() { return id; }
    @Override
    public String toString() { return id; }
}

List<Foo> list = Arrays.asList(new Foo("1"), new Foo("2"), new Foo("3"), new Foo("4"));
List<String> keys = Arrays.asList("2","1","3","4");

// before Java 8
List<Foo> result1 = new ArrayList<>(list);
Collections.sort(result1, new Comparator() {
    @Override
    public int compare(Foo o1, Foo o2) {
        return keys.indexOf(o1.getId()) < keys.indexOf(o2.getId()) ? -1 : 1;
    }
});

// after Java 8
List<Foo> result2 = list.stream()
    .sorted(Comparator.comparing(e -> keys.indexOf(e.getId())))
    .collect(Collectors.toList());

System.out.println(result1); // [2, 1, 3, 4]
System.out.println(result2); // [2, 1, 3, 4]

2018年5月28日 星期一

[PHP] unique values & resort keys

function calc($t0, $t1)
{
    $v0 = array_sum(explode(' ', $t0));
    $v1 = array_sum(explode(' ', $t1));
    $sub = $v1 - $v0;
    echo "$v1 - $v0 = $sub\n";
    return $sub;
}

$a = [116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,117,116,117,116,116,116,116,116,119];

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
    array_values(array_unique($a));
}
$t1 = microtime();
$method1 = calc($t0, $t1);     // output: 1527477436.7125 - 1527477409.434 = 27.278562068939

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
    array_keys(array_flip($a));
}
$t1 = microtime();
$method2 = calc($t0, $t1);     // output: 1527477439.6214 - 1527477436.7126 = 2.908814907074

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
    array_merge(array_flip(array_flip($a)));
}
$t1 = microtime();
$method3 = calc($t0, $t1);     // output: 1527477443.3256 - 1527477439.6215 = 3.7041912078857

有時候直覺的方式會比較慢。

參考資料:http://php.net/manual/en/function.array-unique.php#70786

PHP 5.6.36 (cli) (built: May  9 2018 20:31:47)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
    with Xdebug v2.3.3, Copyright (c) 2002-2015, by Derick Rethans

2018年5月8日 星期二

[Java] find enum with Java 8 streams

import java.util.EnumSet;

public enum Status {
   PENDING("pending"),
   ACTIVE("active");

   public static final Status fromString(String status) {
      return Stream.of(Status.values)
         .filter(e -> e.status.equals(status))
         .findAny();
         .orElseThrow(() -> new IllegalArgumentException("No enum constant " + status));
   }

   private final String status;

   private Status(String status) {
      this.status = status;
   }

   @Override
   public String toString() {
      return status;
   }

}

參考資料:

[Java] running JAX-RS with gretty

開一個專案叫 jersey,然後按下面加入代碼:

// build.gradle
plugins {
    id 'war'
    //id 'org.gretty' version '2.1.0'
    id 'org.akhikhl.gretty' version '2.0.0'
}
// 'jetty7', 'jetty8', 'jetty9', 'jetty9.3', 'jetty9.4', 'tomcat7', 'tomcat8'
//gretty.servletContainer='jetty9'
repositories {
    jcenter()
}
dependencies {
    compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.26'
    compile 'org.glassfish.jersey.inject:jersey-hk2:2.27'
}
// src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
   id="WebApp_ID"
   version="3.0">
   <display-name>My JAX-RS</display-name>
   <servlet>
      <servlet-name>jersey-servlet</servlet-name>
      <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
      <init-param>
         <param-name>jersey.config.server.provider.packages</param-name>
         <param-value>demo</param-value>
      </init-param>
   </servlet>
   <servlet-mapping>
      <servlet-name>jersey-servlet</servlet-name>
      <url-pattern>/*</url-pattern>
   </servlet-mapping>
</web-app>
// src/main/java/demo/Hello.java
package demo;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;

@Path("/hello")
public class Hello {
    @GET()
    public String hello() {
        return "hello";
    }

    @GET
    @Path("/{name}")
    public Response getMessage(@PathParam("name") String name) {
        String outMsg = "Hello " + name + "!";
        return Response.status(200).entity(outMsg).build();
    }
}

接下來執行 gradle appRun

$ gradle appRun
:prepareInplaceWebAppFolder UP-TO-DATE
:createInplaceWebAppFolder UP-TO-DATE
:compileJava
:processResources NO-SOURCE
:classes
:prepareInplaceWebAppClasses
:prepareInplaceWebApp
:appRun
19:46:58 INFO  Jetty 9.2.24.v20180105 started and listening on port 8080
19:46:58 INFO  JAX-RS Application Demo runs at:
19:46:58 INFO    http://localhost:8080/jersey

> Task :appRun
Press any key to stop the server.
<===========--> 87% EXECUTING [17s]
> :appRun

然後就可以測試了:

  • http://localhost:8080/jersey/hello
  • http://localhost:8080/jersey/hello/John

備註:

  • web.xml 的 "param-value" 必須跟套件名一樣,jersey 才會作用。
  • 用 gretty 試跑的時候,URL 是帶上專案名稱,像這裡是 "/jersey"。放在 Servlet 上面跑的時候則是看檔案名稱,例如 "jersey.war";如果不想要這個路徑,可以改成 "ROOT.war"。
  • web.xml 預設路徑是 src/main/webapp/WEB-INF,但是可以在 Gradle 的 war plugin 設定。

備註2:Servlet 3 之後可以不寫 web.xml,改成繼承 ResourceConfig。

// src/main/java/demo/MyApplication.java
package demo;

import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("/")
public class MyApplication extends ResourceConfig {
    public MyApplication() {
        packages("demo");
    }
}

備註3:如果 gretty 發生 Java9 相容問題,

Caused by: java.lang.RuntimeException: Error scanning entry META-INF/versions/9/module-info.class from jar file: .gradle/caches/modules-2/files-2.1/...

Caused by: java.lang.RuntimeException: Error scanning entry module-info.class from jar file: .gradle/caches/modules-2/files-2.1/...
可以設定參數 gretty.servletContainer 為 "jetty9.4" 或是 "tomcat8" 測試。

參考資料:

2016年11月23日 星期三

[PHP] SQLSTATE[HY000]: General error: 2006 MySQL server has gone away

近日遇到稍微罕見的案例:MySQL server has gone away

PHP Warning:  PDO::exec(): MySQL server has gone away in /.../A.inc on line 104
PHP Warning:  PDO::exec(): Error reading result set's header in /.../A.inc on line 104
PHP Fatal error:  Uncaught exception 'PDOException' with message 'SQLSTATE[HY000]: General error: 2006 MySQL server has gone away' in /.../A.inc:104
Stack trace:
#0 /.../A.inc(104): PDO->exec('SET NAMES utf8')
...

由於這是在執行排程的時候遇到,查詢之後還是決定從 PHP 著手,避免影響 APIs 效能。

下面是處理方式;拿掉註解則可以測試狀況。

$pdo = $this->getPdo();
try {
   // $pdo->exec('SET wait_timeout=1');
   // sleep(3);
   $pdo->exec('SET NAMES utf8');
} catch (PDOException $e) {
   $errInfo = $pdo->errorInfo();
   if ($errInfo[0] === 'HY000' && $errInfo[1] === 2006) {
      // SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
      $this->resetPdo();
      $pdo = $this->getPdo();
      $pdo->exec('SET NAMES utf8');
      return $pdo;
   }
   throw $e;
}
return $pdo;

參考資料:

2016年6月7日 星期二

[PHP] empty() / isset() on String Offsets

一般判斷陣列是否有鍵約莫就是 array_key_exists()、empty()、isset()。 最近遇到的問題是,如果需要判斷多於兩層的鍵存不存在,會出現 Fatal error:

$a = '';
var_dump(empty($a['1']['2'])); // bool(true)
var_dump(isset($a['1']['2'])); // bool(false)
var_dump(empty($a['1']['2']['3'])); // PHP Fatal error:  Cannot use string offset as an array
var_dump(isset($a['1']['2']['3'])); // PHP Fatal error:  Cannot use string offset as an array

$a['1'] = '';
var_dump(empty($a['1']['2']['3'])); // bool(true)
var_dump(isset($a['1']['2']['3'])); // bool(false)
var_dump(empty($a['1']['2']['3']['4'])); // PHP Fatal error:  Cannot use string offset as an array
var_dump(isset($a['1']['2']['3']['4'])); // PHP Fatal error:  Cannot use string offset as an array

$a['1']['2'] = '';
var_dump(empty($a['1']['2']['3']['4'])); // bool(true)
var_dump(isset($a['1']['2']['3']['4'])); // bool(false)
var_dump(empty($a['1']['2']['3']['4']['5'])); // PHP Fatal error:  Cannot use string offset as an array
var_dump(isset($a['1']['2']['3']['4']['5'])); // PHP Fatal error:  Cannot use string offset as an array

但是如果有一個鍵不匹配,就不會發生問題:

$a['x'] = '';
var_dump(empty($a['1']['2']['3'])); // bool(true)
var_dump(isset($a['1']['2']['3'])); // bool(false)
var_dump(empty($a['1']['2']['3']['4'])); // bool(true)
var_dump(isset($a['1']['2']['3']['4'])); // bool(false)

$a['1']['y'] = '';
var_dump(empty($a['1']['2']['3']['4'])); // bool(true)
var_dump(isset($a['1']['2']['3']['4'])); // bool(false)
var_dump(empty($a['1']['2']['3']['4']['5'])); // bool(true)
var_dump(isset($a['1']['2']['3']['4']['5'])); // bool(false)

$a['x']['2'] = '';
var_dump(empty($a['1']['2']['3']['4'])); // bool(true)
var_dump(isset($a['1']['2']['3']['4'])); // bool(false)
var_dump(empty($a['1']['2']['3']['4']['5'])); // bool(true)
var_dump(isset($a['1']['2']['3']['4']['5'])); // bool(false)

2015年10月13日 星期二

[PHP] 換行造成的災難

關於 ?> end tag,官網有這麼一段話,建議不要寫:

The closing tag of a PHP block at the end of a file is optional, and in some cases omitting it is helpful when using include or require, so unwanted whitespace will not occur at the end of files, and you will still be able to add headers to the response later. It is also handy if you use output buffering, and would not like to see added unwanted whitespace at the end of the parts generated by the included files.

最近遇到一件慘案。

伺服器A要呼叫伺服器B的一隻 API,因為早期開發比較沒有固定做法,所以把整個資料進行加密回傳給伺服器A。 然後有一天突然說解密錯誤。

測試的時候可以看到,伺服器A拿到的資料會換行,但是伺服器B傳出的時候並沒有換行。示意如下:

// 伺服器A
[Tue Oct 13 11:27:39 2015] [error] [client 127.0.0.1] 
4fCfBODGX4qDIcLjcc0NqbY-

// 伺服器B
[Tue Oct 13 11:27:39 2015] [error] [client 127.0.0.1] 4fCfBODGX4qDIcLjcc0NqbY-

// handler.php
<?php
require_once 'XYZ.php';
// ...

header('Content-Type: application/octet-stream');
header('HTTP/1.0 200');

echo encrypt_data($output);
?>
檢查伺服器B,看起來沒什麼改動。找了很久之後才發現,有人在某隻依賴的 PHP 檔案的 end tag 後面加了一行:
// ABC.php
<?php
// ...
?>

一個小疏忽會累死別人。

[Java] Invalid HTTP method: PATCH

最近系統需要使用 Netty4,所以把衝突的 Netty3 拆掉,然後就出現了例外。 pom.xml <dependency> <groupId>com.ning</groupId> <artifactId>as...