1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.timezone.location.common; 17 18 import androidx.annotation.NonNull; 19 import androidx.annotation.Nullable; 20 21 import java.util.Arrays; 22 import java.util.Objects; 23 import java.util.function.Function; 24 25 /** Utility methods to support {@link PiiLoggable}. */ 26 public final class PiiLoggables { 27 28 static final String NULL_STRING = "null"; 29 static final String REDACTED_STRING = "<redacted>"; 30 PiiLoggables()31 private PiiLoggables() {} 32 33 /** 34 * Returns a null-safe function that calls {@link PiiLoggable#toPiiString(PiiLoggable)} when the 35 * function's argument is not {@code null}. 36 */ 37 @NonNull toPiiStringFunction()38 public static <V extends PiiLoggable> Function<V, String> toPiiStringFunction() { 39 return x -> PiiLoggable.toPiiString(x); 40 } 41 42 /** 43 * An adapter / wrapper for objects that output PII as part of their normal {@link #toString()}. 44 * The referenced value can be {@code null}. 45 * 46 * <p>The implementation contract: 47 * <ul> 48 * <li>{@link #toPiiString()} must print {@code "null"} if the value is null, and may delegate 49 * to the value's {@link #toString()}.</li> 50 * <li>{@link #toString()} must print {@code "null"} if the value is null, or a PII safe string, 51 * e.g. "<redacted>", or just non-PII information. 52 * <li>Implementations may implement {@link #equals(Object)} and {@link #hashCode()}.</li> 53 * </ul> 54 * 55 * <p>See {@link #fromPiiValue(Object)} for a method that returns objects that implement a 56 * minimal contract. 57 */ 58 public interface PiiLoggableValue<V> extends PiiLoggable { 59 /** Returns the held value. */ get()60 @Nullable V get(); 61 } 62 63 /** 64 * Wraps a nullable reference in a {@link PiiLoggableValue}. {@link Object#toString()} 65 * returns the string {@code "<redacted>"}. {@link Object#equals(Object)} and {@link 66 * Object#hashCode()} delegate to {@code value} when not {@code null}. 67 */ 68 @NonNull fromPiiValue(@ullable V value)69 public static <V> PiiLoggableValue<V> fromPiiValue(@Nullable V value) { 70 class PiiLoggableValueImpl<V> implements PiiLoggableValue<V> { 71 72 private final V mValue; 73 74 PiiLoggableValueImpl(V value) { 75 mValue = value; 76 } 77 78 @Override 79 public V get() { 80 return mValue; 81 } 82 83 @Override 84 public String toPiiString() { 85 return String.valueOf(mValue); 86 } 87 88 @Override 89 public String toString() { 90 return mValue == null ? NULL_STRING : REDACTED_STRING; 91 } 92 93 @Override 94 public boolean equals(Object o) { 95 if (this == o) { 96 return true; 97 } 98 if (o == null || getClass() != o.getClass()) { 99 return false; 100 } 101 PiiLoggableValueImpl<?> that = (PiiLoggableValueImpl<?>) o; 102 return Objects.equals(mValue, that.mValue); 103 } 104 105 @Override 106 public int hashCode() { 107 return Objects.hash(mValue); 108 } 109 } 110 return new PiiLoggableValueImpl<V>(value); 111 } 112 113 /** 114 * A convenience method for creating a {@link PiiLoggable} from a {@link String} that doesn't 115 * contain PII. Both {@link PiiLoggable#toString()} and {@link PiiLoggable#toPiiString()} will 116 * return {@code "null"} if the string is {@code null}, or the result of calling {@link 117 * Object#toString()} if the string is not {@code null}. 118 */ 119 @NonNull fromString(@ullable String s)120 public static PiiLoggable fromString(@Nullable String s) { 121 class PiiLoggableString implements PiiLoggable { 122 123 private final String mString; 124 125 public PiiLoggableString(String string) { 126 mString = string; 127 } 128 129 @Override 130 public String toPiiString() { 131 return toString(); 132 } 133 134 @Override 135 public String toString() { 136 return String.valueOf(mString); 137 } 138 139 @Override 140 public boolean equals(Object o) { 141 if (this == o) { 142 return true; 143 } 144 if (o == null || getClass() != o.getClass()) { 145 return false; 146 } 147 PiiLoggableString that = (PiiLoggableString) o; 148 return Objects.equals(mString, that.mString); 149 } 150 151 @Override 152 public int hashCode() { 153 return Objects.hash(mString); 154 } 155 } 156 return new PiiLoggableString(s); 157 } 158 159 /** 160 * Creates a {@link PiiLoggable} that generates strings using the supplied template. The 161 * resulting object uses {@link String#format(String, Object...)} for {@link 162 * PiiLoggable#toString()} and {@link PiiLoggables#formatPiiString(String, PiiLoggable...)} for 163 * {@link PiiLoggable#toPiiString()}. 164 */ 165 @NonNull fromTemplate(@onNull String template, PiiLoggable... piiLoggables)166 public static PiiLoggable fromTemplate(@NonNull String template, PiiLoggable... piiLoggables) { 167 class TemplatedPiiLoggable implements PiiLoggable { 168 @NonNull private final String mTemplate; 169 @NonNull private final PiiLoggable[] mLoggables; 170 171 public TemplatedPiiLoggable(String template, PiiLoggable... loggables) { 172 mTemplate = Objects.requireNonNull(template); 173 mLoggables = Objects.requireNonNull(loggables); 174 } 175 176 @Override 177 public String toString() { 178 return String.format(mTemplate, (Object[]) mLoggables); 179 } 180 181 @Override 182 public String toPiiString() { 183 return PiiLoggables.formatPiiString(mTemplate, mLoggables); 184 } 185 186 @Override 187 public boolean equals(Object o) { 188 if (this == o) { 189 return true; 190 } 191 if (o == null || getClass() != o.getClass()) { 192 return false; 193 } 194 TemplatedPiiLoggable that = (TemplatedPiiLoggable) o; 195 return mTemplate.equals(that.mTemplate) && 196 Arrays.equals(mLoggables, that.mLoggables); 197 } 198 199 @Override 200 public int hashCode() { 201 int result = Objects.hash(mTemplate); 202 result = 31 * result + Arrays.hashCode(mLoggables); 203 return result; 204 } 205 } 206 207 return new TemplatedPiiLoggable(template, piiLoggables); 208 } 209 210 /** 211 * Formats a templated string. This method operates like {@link 212 * String#format(String, Object...)} except {@code %s} will be replaced with the result of 213 * {@link PiiLoggable#toPiiString(PiiLoggable)} instead of {@link String#valueOf(Object)}. 214 */ 215 @NonNull formatPiiString(@onNull String format, PiiLoggable... piiLoggables)216 public static String formatPiiString(@NonNull String format, PiiLoggable... piiLoggables) { 217 String[] strings = new String[piiLoggables.length]; 218 for (int i = 0; i < strings.length; i++) { 219 PiiLoggable piiLoggable = piiLoggables[i]; 220 strings[i] = PiiLoggable.toPiiString(piiLoggable); 221 } 222 return String.format(format, (Object[]) strings); 223 } 224 } 225