Introduce UserInfo helper class

A new helper class UserInfo is introduced that collects
information about a resource or token owner.
The corresponding attributes have been extracted from
the AccessToken class and moved to UserInfo.

This patch also adds unit tests for both classes.

Change-Id: Icedd68d68fb76f00749eee0d242584afb5cb1117
Signed-off-by: Michael Ochmann <michael.ochmann@sap.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cfoauth/AccessToken.java b/src/main/java/com/googlesource/gerrit/plugins/cfoauth/AccessToken.java
index 3e9e4f3..a9af673 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cfoauth/AccessToken.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cfoauth/AccessToken.java
@@ -14,19 +14,26 @@
 
 package com.googlesource.gerrit.plugins.cfoauth;
 
-import com.google.gerrit.reviewdb.client.AccountExternalId;
-
+import java.io.Serializable;
 import java.util.Objects;
 
-class AccessToken {
+class AccessToken implements Serializable {
 
+  private static final long serialVersionUID = 1L;
+
+  private final UserInfo userInfo;
   private final String value;
-
-  private final String externalId;
-  private final String username;
-  private final String emailAddress;
   private final long expiresAt;
 
+  /** Representation of an undefined access token, which
+   * has no owner and no value.
+   */
+  static final AccessToken UNDEFINED = new AccessToken();
+
+  private AccessToken() {
+    this("", "", "", 0);
+  }
+
   /**
    * Creates an access token.
    *
@@ -36,79 +43,58 @@
    * @param expiresAt time to expiration of this tokens in seconds
    * since midnight January, 1st, 1970.
    */
-  public AccessToken(String value, String username, String emailAddress,
+  AccessToken(String value, String username, String emailAddress,
       long expiresAt) {
     if (value == null) {
       throw new IllegalArgumentException("token value must not be null");
     }
-    if (username == null) {
-      throw new IllegalArgumentException("username must not be null");
-    }
-    if (emailAddress == null) {
-      throw new IllegalArgumentException("emailAddress must not be null");
-    }
+    this.userInfo = new UserInfo(username, emailAddress);
     this.value = value;
-    this.username = username;
-    this.externalId = AccountExternalId.SCHEME_EXTERNAL + username;
-    this.emailAddress = emailAddress;
     this.expiresAt = expiresAt;
   }
 
   /**
    * Returns the value of the access token.
    */
-  public String getValue() {
+  String getValue() {
     return value;
   }
 
   /**
-   * Returns the external id of the token owner.
-   */
-  public String getExternalId() {
-    return externalId;
-  }
-
-  /**
-   * Returns the name of the token owner.
-   */
-  public String getUserName() {
-    return username;
-  }
-
-  /**
-   * Returns the email address of the token owner.
-   */
-  public String getEmailAddress() {
-    return emailAddress;
-  }
-
-  /**
    * Returns the timestamp when this token will expire in seconds
    * since midnight January, 1st, 1970.
    */
-  public long getExpiresAt() {
+  long getExpiresAt() {
     return expiresAt;
   }
 
   /**
    * Returns <code>true</code> if this token has already expired.
    */
-  public boolean isExpired() {
+  boolean isExpired() {
     return System.currentTimeMillis() > expiresAt * 1000;
   }
 
+  /**
+   * Returns information about the token owner.
+   */
+  UserInfo getUserInfo() {
+    return userInfo;
+  }
+
   @Override
   public String toString() {
     return "{'value':'" + value
-        + "','externalId':'" + externalId
-        + "','userName':'" + username
-        + "','emailAddress':'" + emailAddress
+        + "','externalId':'" + userInfo.getExternalId()
+        + "','username':'" + userInfo.getUserName()
+        + "','emailAddress':'" + userInfo.getEmailAddress()
+        + "','displayName':'" + userInfo.getDisplayName()
         + "','expiresAt':" + expiresAt + "}";
   }
 
   @Override
   public int hashCode() {
-    return value.hashCode();
+    return Objects.hash(value, expiresAt, userInfo);
   }
 
   @Override
@@ -119,6 +105,9 @@
     if (!(obj instanceof AccessToken)) {
       return false;
     }
-    return Objects.equals(value, ((AccessToken) obj).value);
+    AccessToken accessToken = (AccessToken) obj;
+    return value.equals(accessToken.value) &&
+        expiresAt == accessToken.expiresAt &&
+        userInfo.equals(accessToken.userInfo);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cfoauth/CFOAuthService.java b/src/main/java/com/googlesource/gerrit/plugins/cfoauth/CFOAuthService.java
index fd4b536..cad17eb 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cfoauth/CFOAuthService.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cfoauth/CFOAuthService.java
@@ -66,8 +66,9 @@
   @Override
   public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
     AccessToken accessToken = uaaClient.toAccessToken(token.getToken());
-    String displayName = uaaClient.getDisplayName(token.getToken());
-    return getAsOAuthUserInfo(accessToken, displayName);
+    UserInfo userInfo = accessToken.getUserInfo();
+    userInfo.setDisplayName(uaaClient.getDisplayName(token.getToken()));
+    return getAsOAuthUserInfo(userInfo);
   }
 
   @Override
@@ -80,14 +81,13 @@
     return NAME;
   }
 
-  private OAuthToken getAsOAuthToken(AccessToken accessToken) {
+  private static OAuthToken getAsOAuthToken(AccessToken accessToken) {
     return new OAuthToken(accessToken.getValue(), null, null);
   }
 
-  private OAuthUserInfo getAsOAuthUserInfo(AccessToken accessToken,
-      String displayName) {
-    return new OAuthUserInfo(accessToken.getExternalId(),
-        accessToken.getUserName(), accessToken.getEmailAddress(),
-        displayName, null);
+  private static OAuthUserInfo getAsOAuthUserInfo(UserInfo userInfo) {
+    return new OAuthUserInfo(userInfo.getExternalId(),
+        userInfo.getUserName(), userInfo.getEmailAddress(),
+        userInfo.getDisplayName(), null);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cfoauth/UserInfo.java b/src/main/java/com/googlesource/gerrit/plugins/cfoauth/UserInfo.java
new file mode 100644
index 0000000..c9418f5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/cfoauth/UserInfo.java
@@ -0,0 +1,128 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// 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 com.googlesource.gerrit.plugins.cfoauth;
+
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+class UserInfo implements Serializable {
+
+  private static final long serialVersionUID = 1L;
+
+  private final String externalId;
+  private final String username;
+  private final String emailAddress;
+  private String displayName;
+
+  /**
+   * Creates a user info object.
+   *
+   * @param username the name of a resource owner.
+   * @param emailAddress the email address of a resource owner.
+   */
+  UserInfo(String username, String emailAddress) {
+    if (username == null) {
+      throw new IllegalArgumentException("username must not be null");
+    }
+    if (emailAddress == null) {
+      throw new IllegalArgumentException("emailAddress must not be null");
+    }
+    this.username = username;
+    this.externalId = AccountExternalId.SCHEME_EXTERNAL + username;
+    this.emailAddress = emailAddress;
+    this.displayName = username;
+  }
+
+  /**
+   * Creates a user info object.
+   *
+   * @param username the name of a resource owner.
+   * @param emailAddress the email address of a resource owner.
+   * @param displayName the display name of a resource owner or
+   * <code>null</code>. In that case the {@link #getUserName()}
+   * will be assigned.
+   */
+  UserInfo(String username, String emailAddress, String displayName) {
+    this(username, emailAddress);
+    setDisplayName(displayName);
+  }
+
+  /**
+   * Returns the external id of the resource owner.
+   */
+  String getExternalId() {
+    return externalId;
+  }
+
+  /**
+   * Returns the name of the resource owner.
+   */
+  String getUserName() {
+    return username;
+  }
+
+  /**
+   * Returns the email address of the resource owner.
+   */
+  String getEmailAddress() {
+    return emailAddress;
+  }
+
+  /**
+   * Returns the display name of the resource owner.
+   */
+  String getDisplayName() {
+    return displayName;
+  }
+
+  /**
+   * Sets the display name of the resource owner.
+   *
+   * @param displayName the display name of a resource owner or
+   * <code>null</code>. In that case {@link #getUserName()}
+   * will be assigned.
+   */
+  void setDisplayName(String displayName) {
+    this.displayName = displayName != null? displayName : username;
+  }
+
+  @Override
+  public String toString() {
+    return "{externalId':'" + externalId
+        + "','username':'" + username
+        + "','emailAddress':'" + emailAddress
+        + "','displayName':'" + displayName + "'}";
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(username, emailAddress);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+        return true;
+    }
+    if (!(obj instanceof UserInfo)) {
+      return false;
+    }
+    UserInfo userInfo = (UserInfo) obj;
+    return username.equals(userInfo.username) &&
+        emailAddress.equals(userInfo.emailAddress);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/cfoauth/AccessTokenTest.java b/src/test/java/com/googlesource/gerrit/plugins/cfoauth/AccessTokenTest.java
new file mode 100644
index 0000000..a9cf635
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/cfoauth/AccessTokenTest.java
@@ -0,0 +1,85 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// 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 com.googlesource.gerrit.plugins.cfoauth;
+
+import static com.googlesource.gerrit.plugins.cfoauth.AccessToken.UNDEFINED;
+import static com.googlesource.gerrit.plugins.cfoauth.TestUtils.*;
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class AccessTokenTest {
+
+  private static final String TOKEN_VALUE = "tokenvalue";
+  private static final String ANOTHER_TOKEN_VALUE = "anothertokenvalue";
+  private static final long EXPIRES_AT = 4711L;
+
+  private static final AccessToken TOKEN =
+      new AccessToken(TOKEN_VALUE, FOO, BAR, EXPIRES_AT);
+  private static AccessToken TOKEN_DIFFERENT_VALUE =
+      new AccessToken(ANOTHER_TOKEN_VALUE, FOO, BAR, EXPIRES_AT);
+  private static AccessToken TOKEN_DIFFERENT_NAME =
+      new AccessToken(TOKEN_VALUE, ANOTHER_FOO, BAR, EXPIRES_AT);
+  private static AccessToken TOKEN_DIFFERENT_EMAIL =
+      new AccessToken(TOKEN_VALUE, FOO, ANOTHER_BAR, EXPIRES_AT);
+  private static final AccessToken TOKEN_DIFFERENT_EXPIRES =
+      new AccessToken(TOKEN_VALUE, FOO, BAR, EXPIRES_AT + 1);
+
+  @Test
+  public void testCreateAccessToken() throws Exception {
+    assertAccessToken(TOKEN, FOO, BAR, null, TOKEN_VALUE, EXPIRES_AT);
+  }
+
+  @Test
+  public void testUndefined() throws Exception {
+    assertAccessToken(UNDEFINED, "", "", "", "", 0);
+    assertTrue(UNDEFINED.isExpired());
+  }
+
+  @Test
+  public void testExpiresAt() throws Exception {
+    assertTrue(TOKEN.isExpired());
+    assertFalse(new AccessToken(TOKEN_VALUE, FOO, BAR,
+        System.currentTimeMillis() + 10).isExpired());
+  }
+
+  @Test
+  public void testEquals() throws Exception {
+    assertTrue(TOKEN.equals(TOKEN));
+    assertFalse(TOKEN.equals(TOKEN_DIFFERENT_VALUE));
+    assertFalse(TOKEN_DIFFERENT_VALUE.equals(TOKEN));
+    assertFalse(TOKEN.equals(TOKEN_DIFFERENT_NAME));
+    assertFalse(TOKEN_DIFFERENT_NAME.equals(TOKEN));
+    assertFalse(TOKEN.equals(TOKEN_DIFFERENT_EMAIL));
+    assertFalse(TOKEN_DIFFERENT_EXPIRES.equals(TOKEN));
+    assertFalse(TOKEN.equals(TOKEN_DIFFERENT_EXPIRES));
+    assertFalse(TOKEN.equals(null));
+    assertFalse(TOKEN.equals(MR_FOO));
+  }
+
+  @Test(expected=IllegalArgumentException.class)
+  public void testMissingValue() throws Exception {
+    new AccessToken(null, FOO, BAR, EXPIRES_AT);
+  }
+
+  private void assertAccessToken(AccessToken accessToken, String username,
+      String emailAddress, String displayName, String value, long expiresAt) {
+    assertUserInfo(accessToken.getUserInfo(), username,
+        emailAddress, displayName);
+    assertEquals(value, accessToken.getValue());
+    assertEquals(expiresAt, accessToken.getExpiresAt());
+    assertTrue(accessToken.isExpired());
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/cfoauth/TestUtils.java b/src/test/java/com/googlesource/gerrit/plugins/cfoauth/TestUtils.java
new file mode 100644
index 0000000..16e9c76
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/cfoauth/TestUtils.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// 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 com.googlesource.gerrit.plugins.cfoauth;
+
+import static org.junit.Assert.assertEquals;
+
+public class TestUtils {
+
+  static final String FOO = "foo";
+  static final String BAR = "bar";
+  static final String MR_FOO = "Mr. Foo";
+  static final String ANOTHER_FOO = "anotherfoo";
+  static final String ANOTHER_BAR = "anotherbar";
+
+  static void assertUserInfo(UserInfo userInfo, String username,
+      String emailAddress, String displayName) {
+    assertEquals(username, userInfo.getUserName());
+    assertEquals(emailAddress, userInfo.getEmailAddress());
+    if (displayName != null) {
+      assertEquals(displayName, userInfo.getDisplayName());
+    } else {
+      assertEquals(username, userInfo.getDisplayName());
+    }
+  }
+
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/cfoauth/UAAClientTest.java b/src/test/java/com/googlesource/gerrit/plugins/cfoauth/UAAClientTest.java
index 160b8ea..2616784 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/cfoauth/UAAClientTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/cfoauth/UAAClientTest.java
@@ -174,8 +174,9 @@
   private void assertHS266AccessToken(AccessToken accessToken) {
     assertEquals(HS256_TEST_TOKEN, accessToken.getValue());
     assertEquals(1436232932L, accessToken.getExpiresAt());
-    assertEquals("external:marissa", accessToken.getExternalId());
-    assertEquals("marissa", accessToken.getUserName());
-    assertEquals("marissa@test.org", accessToken.getEmailAddress());
+    UserInfo userInfo = accessToken.getUserInfo();
+    assertEquals("external:marissa", userInfo.getExternalId());
+    assertEquals("marissa", userInfo.getUserName());
+    assertEquals("marissa@test.org", userInfo.getEmailAddress());
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/cfoauth/UserInfoTest.java b/src/test/java/com/googlesource/gerrit/plugins/cfoauth/UserInfoTest.java
new file mode 100644
index 0000000..cb1eb32
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/cfoauth/UserInfoTest.java
@@ -0,0 +1,71 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// 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 com.googlesource.gerrit.plugins.cfoauth;
+
+import static com.googlesource.gerrit.plugins.cfoauth.TestUtils.*;
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class UserInfoTest {
+
+  private static final UserInfo USER = new UserInfo(FOO, BAR);
+  private static final UserInfo USER_DIFFERENT_NAME =
+      new UserInfo(ANOTHER_FOO, BAR);
+  private static final UserInfo USER_DIFFERENT_EMAIL =
+      new UserInfo(FOO, ANOTHER_BAR);
+  private static final UserInfo USER_DISPLAYNAME =
+      new UserInfo(FOO, BAR, MR_FOO);
+
+  @Test
+  public void testCreateUserInfo() throws Exception {
+    assertUserInfo(USER, FOO, BAR, null);
+    assertUserInfo(USER_DISPLAYNAME, FOO, BAR, MR_FOO);
+  }
+
+  @Test
+  public void testDisplayName() throws Exception {
+    UserInfo userInfo = new UserInfo(FOO, BAR);
+    assertUserInfo(userInfo, FOO, BAR, null);
+    userInfo.setDisplayName(MR_FOO);
+    assertUserInfo(userInfo, FOO, BAR, MR_FOO);
+    userInfo.setDisplayName(null);
+    assertUserInfo(userInfo, FOO, BAR, null);
+  }
+
+  @Test
+  public void testEquals() throws Exception {
+    assertTrue(USER.equals(USER));
+    assertFalse(USER.equals(USER_DIFFERENT_NAME));
+    assertFalse(USER_DIFFERENT_NAME.equals(USER));
+    assertFalse(USER.equals(USER_DIFFERENT_EMAIL));
+    assertFalse(USER_DIFFERENT_EMAIL.equals(USER));
+    assertTrue(USER.equals(USER_DISPLAYNAME));
+    assertTrue(USER_DISPLAYNAME.equals(USER));
+    assertFalse(USER.equals(null));
+    assertFalse(USER.equals(MR_FOO));
+  }
+
+  @Test(expected=IllegalArgumentException.class)
+  public void testMissingUserName() throws Exception {
+    new UserInfo(null, BAR);
+  }
+
+  @Test(expected=IllegalArgumentException.class)
+  public void testMissingEmailAddress() throws Exception {
+    new UserInfo(FOO, null);
+  }
+
+}