Commit 5d7c4150 authored by shengnan hu's avatar shengnan hu
Browse files

init

parents
Pipeline #4715 failed with stage
in 30 seconds
package com.taobao.arthas.common;
/**
*
* @author hengyunabc 2020-09-02
*
*/
public class ArthasConstants {
/**
* local address in VM communication
*
* @see io.netty.channel.local.LocalAddress
* @see io.netty.channel.local.LocalChannel
*/
public static final String NETTY_LOCAL_ADDRESS = "arthas-netty-LocalAddress";
public static final int MAX_HTTP_CONTENT_LENGTH = 1024 * 1024 * 10;
public static final String ARTHAS_OUTPUT = "arthas-output";
public static final String APP_NAME = "app-name";
public static final String PROJECT_NAME = "project.name";
public static final String SPRING_APPLICATION_NAME = "spring.application.name";
public static final int TELNET_PORT = 3658;
public static final String DEFAULT_WEBSOCKET_PATH = "/ws";
public static final int WEBSOCKET_IDLE_SECONDS = 60;
/**
* HTTP cookie id
*/
public static final String ASESSION_KEY = "asession";
public static final String DEFAULT_USERNAME = "arthas";
public static final String SUBJECT_KEY = "subject";
public static final String AUTH = "auth";
public static final String USERNAME_KEY = "username";
public static final String PASSWORD_KEY = "password";
}
package com.taobao.arthas.common;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A class for executing on the command line and returning the result of
* execution.
*
* @author alessandro[at]perucchi[dot]org
*/
public class ExecutingCommand {
private ExecutingCommand() {
}
/**
* Executes a command on the native command line and returns the result.
*
* @param cmdToRun
* Command to run
* @return A list of Strings representing the result of the command, or empty
* string if the command failed
*/
public static List<String> runNative(String cmdToRun) {
String[] cmd = cmdToRun.split(" ");
return runNative(cmd);
}
/**
* Executes a command on the native command line and returns the result line by
* line.
*
* @param cmdToRunWithArgs
* Command to run and args, in an array
* @return A list of Strings representing the result of the command, or empty
* string if the command failed
*/
public static List<String> runNative(String[] cmdToRunWithArgs) {
Process p = null;
try {
p = Runtime.getRuntime().exec(cmdToRunWithArgs);
} catch (SecurityException e) {
AnsiLog.trace("Couldn't run command {}:", Arrays.toString(cmdToRunWithArgs));
AnsiLog.trace(e);
return new ArrayList<String>(0);
} catch (IOException e) {
AnsiLog.trace("Couldn't run command {}:", Arrays.toString(cmdToRunWithArgs));
AnsiLog.trace(e);
return new ArrayList<String>(0);
}
ArrayList<String> sa = new ArrayList<String>();
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
try {
String line;
while ((line = reader.readLine()) != null) {
sa.add(line);
}
p.waitFor();
} catch (IOException e) {
AnsiLog.trace("Problem reading output from {}:", Arrays.toString(cmdToRunWithArgs));
AnsiLog.trace(e);
return new ArrayList<String>(0);
} catch (InterruptedException ie) {
AnsiLog.trace("Problem reading output from {}:", Arrays.toString(cmdToRunWithArgs));
AnsiLog.trace(ie);
Thread.currentThread().interrupt();
} finally {
IOUtils.close(reader);
}
return sa;
}
/**
* Return first line of response for selected command.
*
* @param cmd2launch
* String command to be launched
* @return String or empty string if command failed
*/
public static String getFirstAnswer(String cmd2launch) {
return getAnswerAt(cmd2launch, 0);
}
/**
* Return response on selected line index (0-based) after running selected
* command.
*
* @param cmd2launch
* String command to be launched
* @param answerIdx
* int index of line in response of the command
* @return String whole line in response or empty string if invalid index or
* running of command fails
*/
public static String getAnswerAt(String cmd2launch, int answerIdx) {
List<String> sa = ExecutingCommand.runNative(cmd2launch);
if (answerIdx >= 0 && answerIdx < sa.size()) {
return sa.get(answerIdx);
}
return "";
}
}
package com.taobao.arthas.common;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
*
* @see org.apache.commons.io.FileUtils
* @author hengyunabc 2020-05-03
*
*/
public class FileUtils {
public static File getTempDirectory() {
return new File(System.getProperty("java.io.tmpdir"));
}
/**
* Writes a byte array to a file creating the file if it does not exist.
* <p>
* NOTE: As from v1.3, the parent directories of the file will be created if
* they do not exist.
*
* @param file the file to write to
* @param data the content to write to the file
* @throws IOException in case of an I/O error
* @since 1.1
*/
public static void writeByteArrayToFile(final File file, final byte[] data) throws IOException {
writeByteArrayToFile(file, data, false);
}
/**
* Writes a byte array to a file creating the file if it does not exist.
*
* @param file the file to write to
* @param data the content to write to the file
* @param append if {@code true}, then bytes will be added to the end of the
* file rather than overwriting
* @throws IOException in case of an I/O error
* @since 2.1
*/
public static void writeByteArrayToFile(final File file, final byte[] data, final boolean append)
throws IOException {
writeByteArrayToFile(file, data, 0, data.length, append);
}
/**
* Writes {@code len} bytes from the specified byte array starting at offset
* {@code off} to a file, creating the file if it does not exist.
*
* @param file the file to write to
* @param data the content to write to the file
* @param off the start offset in the data
* @param len the number of bytes to write
* @throws IOException in case of an I/O error
* @since 2.5
*/
public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len)
throws IOException {
writeByteArrayToFile(file, data, off, len, false);
}
/**
* Writes {@code len} bytes from the specified byte array starting at offset
* {@code off} to a file, creating the file if it does not exist.
*
* @param file the file to write to
* @param data the content to write to the file
* @param off the start offset in the data
* @param len the number of bytes to write
* @param append if {@code true}, then bytes will be added to the end of the
* file rather than overwriting
* @throws IOException in case of an I/O error
* @since 2.5
*/
public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len,
final boolean append) throws IOException {
FileOutputStream out = null;
try {
out = openOutputStream(file, append);
out.write(data, off, len);
} finally {
IOUtils.close(out);
}
}
/**
* Opens a {@link FileOutputStream} for the specified file, checking and
* creating the parent directory if it does not exist.
* <p>
* At the end of the method either the stream will be successfully opened, or an
* exception will have been thrown.
* <p>
* The parent directory will be created if it does not exist. The file will be
* created if it does not exist. An exception is thrown if the file object
* exists but is a directory. An exception is thrown if the file exists but
* cannot be written to. An exception is thrown if the parent directory cannot
* be created.
*
* @param file the file to open for output, must not be {@code null}
* @param append if {@code true}, then bytes will be added to the end of the
* file rather than overwriting
* @return a new {@link FileOutputStream} for the specified file
* @throws IOException if the file object is a directory
* @throws IOException if the file cannot be written to
* @throws IOException if a parent directory needs creating but that fails
* @since 2.1
*/
public static FileOutputStream openOutputStream(final File file, final boolean append) throws IOException {
if (file.exists()) {
if (file.isDirectory()) {
throw new IOException("File '" + file + "' exists but is a directory");
}
if (!file.canWrite()) {
throw new IOException("File '" + file + "' cannot be written to");
}
} else {
final File parent = file.getParentFile();
if (parent != null) {
if (!parent.mkdirs() && !parent.isDirectory()) {
throw new IOException("Directory '" + parent + "' could not be created");
}
}
}
return new FileOutputStream(file, append);
}
/**
* Reads the contents of a file into a byte array.
* The file is always closed.
*
* @param file the file to read, must not be {@code null}
* @return the file contents, never {@code null}
* @throws IOException in case of an I/O error
* @since 1.1
*/
public static byte[] readFileToByteArray(final File file) throws IOException {
InputStream in = null;
try {
in = new FileInputStream(file);
return IOUtils.getBytes(in);
} finally {
IOUtils.close(in);
}
}
}
package com.taobao.arthas.common;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
*
* @author hengyunabc 2018-11-06
*
*/
public class IOUtils {
private IOUtils() {
}
public static String toString(InputStream inputStream) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, length);
}
return result.toString("UTF-8");
}
public static void copy(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
/**
* @return a byte[] containing the information contained in the specified
* InputStream.
* @throws java.io.IOException
*/
public static byte[] getBytes(InputStream input) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
copy(input, result);
result.close();
return result.toByteArray();
}
public static IOException close(InputStream input) {
return close((Closeable) input);
}
public static IOException close(OutputStream output) {
return close((Closeable) output);
}
public static IOException close(final Reader input) {
return close((Closeable) input);
}
public static IOException close(final Writer output) {
return close((Closeable) output);
}
public static IOException close(final Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (final IOException ioe) {
return ioe;
}
return null;
}
// support jdk6
public static IOException close(final ZipFile zip) {
try {
if (zip != null) {
zip.close();
}
} catch (final IOException ioe) {
return ioe;
}
return null;
}
public static boolean isSubFile(File parent, File child) throws IOException {
return child.getCanonicalPath().startsWith(parent.getCanonicalPath() + File.separator);
}
public static boolean isSubFile(String parent, String child) throws IOException {
return isSubFile(new File(parent), new File(child));
}
public static void unzip(String zipFile, String extractFolder) throws IOException {
File file = new File(zipFile);
ZipFile zip = null;
try {
int BUFFER = 1024 * 8;
zip = new ZipFile(file);
File newPath = new File(extractFolder);
newPath.mkdirs();
Enumeration<? extends ZipEntry> zipFileEntries = zip.entries();
// Process each entry
while (zipFileEntries.hasMoreElements()) {
// grab a zip file entry
ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();
String currentEntry = entry.getName();
File destFile = new File(newPath, currentEntry);
if (!isSubFile(newPath, destFile)) {
throw new IOException("Bad zip entry: " + currentEntry);
}
// destFile = new File(newPath, destFile.getName());
File destinationParent = destFile.getParentFile();
// create the parent directory structure if needed
destinationParent.mkdirs();
if (!entry.isDirectory()) {
BufferedInputStream is = null;
BufferedOutputStream dest = null;
try {
is = new BufferedInputStream(zip.getInputStream(entry));
int currentByte;
// establish buffer for writing file
byte data[] = new byte[BUFFER];
// write the current file to disk
FileOutputStream fos = new FileOutputStream(destFile);
dest = new BufferedOutputStream(fos, BUFFER);
// read and write until last byte is encountered
while ((currentByte = is.read(data, 0, BUFFER)) != -1) {
dest.write(data, 0, currentByte);
}
dest.flush();
} finally {
close(dest);
close(is);
}
}
}
} finally {
close(zip);
}
}
}
package com.taobao.arthas.common;
import java.util.Properties;
/**
*
* @author hengyunabc 2018-11-21
*
*/
public class JavaVersionUtils {
private static final String VERSION_PROP_NAME = "java.specification.version";
private static final String JAVA_VERSION_STR = System.getProperty(VERSION_PROP_NAME);
private static final float JAVA_VERSION = Float.parseFloat(JAVA_VERSION_STR);
private JavaVersionUtils() {
}
public static String javaVersionStr() {
return JAVA_VERSION_STR;
}
public static String javaVersionStr(Properties props) {
return (null != props) ? props.getProperty(VERSION_PROP_NAME): null;
}
public static float javaVersion() {
return JAVA_VERSION;
}
public static boolean isJava6() {
return JAVA_VERSION_STR.equals("1.6");
}
public static boolean isJava7() {
return JAVA_VERSION_STR.equals("1.7");
}
public static boolean isJava8() {
return JAVA_VERSION_STR.equals("1.8");
}
public static boolean isJava9() {
return JAVA_VERSION_STR.equals("9");
}
public static boolean isLessThanJava9() {
return JAVA_VERSION < 9.0f;
}
public static boolean isGreaterThanJava7() {
return JAVA_VERSION > 1.7f;
}
public static boolean isGreaterThanJava8() {
return JAVA_VERSION > 1.8f;
}
public static boolean isGreaterThanJava11() {
return JAVA_VERSION > 11.0f;
}
}
package com.taobao.arthas.common;
import java.util.Locale;
/**
*
* @author hengyunabc 2018-11-08
*
*/
public class OSUtils {
private static final String OPERATING_SYSTEM_NAME = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
private static final String OPERATING_SYSTEM_ARCH = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH);
private static final String UNKNOWN = "unknown";
static PlatformEnum platform;
static String arch;
static {
if (OPERATING_SYSTEM_NAME.startsWith("linux")) {
platform = PlatformEnum.LINUX;
} else if (OPERATING_SYSTEM_NAME.startsWith("mac") || OPERATING_SYSTEM_NAME.startsWith("darwin")) {
platform = PlatformEnum.MACOSX;
} else if (OPERATING_SYSTEM_NAME.startsWith("windows")) {
platform = PlatformEnum.WINDOWS;
} else {
platform = PlatformEnum.UNKNOWN;
}
arch = normalizeArch(OPERATING_SYSTEM_ARCH);
}
private OSUtils() {
}
public static boolean isWindows() {
return platform == PlatformEnum.WINDOWS;
}
public static boolean isLinux() {
return platform == PlatformEnum.LINUX;
}
public static boolean isMac() {
return platform == PlatformEnum.MACOSX;
}
public static boolean isCygwinOrMinGW() {
if (isWindows()) {
if ((System.getenv("MSYSTEM") != null && System.getenv("MSYSTEM").startsWith("MINGW"))
|| "/bin/bash".equals(System.getenv("SHELL"))) {
return true;
}
}
return false;
}
public static String arch() {
return arch;
}
public static boolean isArm32() {
return "arm_32".equals(arch);
}
public static boolean isArm64() {
return "aarch_64".equals(arch);
}
private static String normalizeArch(String value) {
value = normalize(value);
if (value.matches("^(x8664|amd64|ia32e|em64t|x64)$")) {
return "x86_64";
}
if (value.matches("^(x8632|x86|i[3-6]86|ia32|x32)$")) {
return "x86_32";
}
if (value.matches("^(ia64w?|itanium64)$")) {
return "itanium_64";
}
if ("ia64n".equals(value)) {
return "itanium_32";
}
if (value.matches("^(sparc|sparc32)$")) {
return "sparc_32";
}
if (value.matches("^(sparcv9|sparc64)$")) {
return "sparc_64";
}
if (value.matches("^(arm|arm32)$")) {
return "arm_32";
}
if ("aarch64".equals(value)) {
return "aarch_64";
}
if (value.matches("^(mips|mips32)$")) {
return "mips_32";
}
if (value.matches("^(mipsel|mips32el)$")) {
return "mipsel_32";
}
if ("mips64".equals(value)) {
return "mips_64";
}
if ("mips64el".equals(value)) {
return "mipsel_64";
}
if (value.matches("^(ppc|ppc32)$")) {
return "ppc_32";
}
if (value.matches("^(ppcle|ppc32le)$")) {
return "ppcle_32";
}
if ("ppc64".equals(value)) {
return "ppc_64";
}
if ("ppc64le".equals(value)) {
return "ppcle_64";
}
if ("s390".equals(value)) {
return "s390_32";
}
if ("s390x".equals(value)) {
return "s390_64";
}
return UNKNOWN;
}
private static String normalize(String value) {
if (value == null) {
return "";
}
return value.toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
}
}
package com.taobao.arthas.common;
public class Pair<X, Y> {
private final X x;
private final Y y;
public Pair(X x, Y y) {
this.x = x;
this.y = y;
}
public X getFirst() {
return x;
}
public Y getSecond() {
return y;
}
public static <A, B> Pair<A, B> make(A a, B b) {
return new Pair<A, B>(a, b);
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Pair))
return false;
Pair other = (Pair) o;
if (x == null) {
if (other.x != null)
return false;
} else {
if (!x.equals(other.x))
return false;
}
if (y == null) {
if (other.y != null)
return false;
} else {
if (!y.equals(other.y))
return false;
}
return true;
}
@Override
public int hashCode() {
int hashCode = 1;
if (x != null)
hashCode = x.hashCode();
if (y != null)
hashCode = (hashCode * 31) + y.hashCode();
return hashCode;
}
@Override
public String toString() {
return "P[" + x + "," + y + "]";
}
}
package com.taobao.arthas.common;
import java.lang.management.ManagementFactory;
import java.util.Map;
/**
*
* @author hengyunabc 2019-02-16
*
*/
public class PidUtils {
private static String PID = "-1";
private static long pid = -1;
private static String MAIN_CLASS = "";
static {
// https://stackoverflow.com/a/7690178
try {
String jvmName = ManagementFactory.getRuntimeMXBean().getName();
int index = jvmName.indexOf('@');
if (index > 0) {
PID = Long.toString(Long.parseLong(jvmName.substring(0, index)));
pid = Long.parseLong(PID);
}
} catch (Throwable e) {
// ignore
}
try {
for (final Map.Entry<String, String> entry : System.getenv().entrySet()) {
if (entry.getKey().startsWith("JAVA_MAIN_CLASS")) // like JAVA_MAIN_CLASS_13328
MAIN_CLASS = entry.getValue();
}
} catch (Throwable e) {
// ignore
}
}
private PidUtils() {
}
public static String currentPid() {
return PID;
}
public static long currentLongPid() {
return pid;
}
public static String mainClass() {
return MAIN_CLASS;
}
}
package com.taobao.arthas.common;
/**
* Enum of supported operating systems.
*
*/
public enum PlatformEnum {
/**
* Microsoft Windows
*/
WINDOWS,
/**
* A flavor of Linux
*/
LINUX,
/**
* macOS (OS X)
*/
MACOSX,
UNKNOWN
}
\ No newline at end of file
package com.taobao.arthas.common;
public class ReflectException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Throwable cause;
public ReflectException(Throwable cause) {
super(cause.getClass().getName() + "-->" + cause.getMessage());
this.cause = cause;
}
public Throwable getCause() {
return this.cause;
}
}
\ No newline at end of file
package com.taobao.arthas.common;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* from spring
* @version $Id: ReflectUtils.java,v 1.30 2009/01/11 19:47:49 herbyderby Exp $
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ReflectUtils {
private ReflectUtils() {
}
private static final Map primitives = new HashMap(8);
private static final Map transforms = new HashMap(8);
private static final ClassLoader defaultLoader = ReflectUtils.class.getClassLoader();
// SPRING PATCH BEGIN
private static final Method privateLookupInMethod;
private static final Method lookupDefineClassMethod;
private static final Method classLoaderDefineClassMethod;
private static final ProtectionDomain PROTECTION_DOMAIN;
private static final Throwable THROWABLE;
private static final List<Method> OBJECT_METHODS = new ArrayList<Method>();
static {
Method privateLookupIn;
Method lookupDefineClass;
Method classLoaderDefineClass;
ProtectionDomain protectionDomain;
Throwable throwable = null;
try {
privateLookupIn = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
try {
return MethodHandles.class.getMethod("privateLookupIn", Class.class,
MethodHandles.Lookup.class);
} catch (NoSuchMethodException ex) {
return null;
}
}
});
lookupDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
try {
return MethodHandles.Lookup.class.getMethod("defineClass", byte[].class);
} catch (NoSuchMethodException ex) {
return null;
}
}
});
classLoaderDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
return ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE,
Integer.TYPE, ProtectionDomain.class);
}
});
protectionDomain = getProtectionDomain(ReflectUtils.class);
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
Method[] methods = Object.class.getDeclaredMethods();
for (Method method : methods) {
if ("finalize".equals(method.getName())
|| (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) {
continue;
}
OBJECT_METHODS.add(method);
}
return null;
}
});
} catch (Throwable t) {
privateLookupIn = null;
lookupDefineClass = null;
classLoaderDefineClass = null;
protectionDomain = null;
throwable = t;
}
privateLookupInMethod = privateLookupIn;
lookupDefineClassMethod = lookupDefineClass;
classLoaderDefineClassMethod = classLoaderDefineClass;
PROTECTION_DOMAIN = protectionDomain;
THROWABLE = throwable;
}
// SPRING PATCH END
private static final String[] CGLIB_PACKAGES = { "java.lang", };
static {
primitives.put("byte", Byte.TYPE);
primitives.put("char", Character.TYPE);
primitives.put("double", Double.TYPE);
primitives.put("float", Float.TYPE);
primitives.put("int", Integer.TYPE);
primitives.put("long", Long.TYPE);
primitives.put("short", Short.TYPE);
primitives.put("boolean", Boolean.TYPE);
transforms.put("byte", "B");
transforms.put("char", "C");
transforms.put("double", "D");
transforms.put("float", "F");
transforms.put("int", "I");
transforms.put("long", "J");
transforms.put("short", "S");
transforms.put("boolean", "Z");
}
public static ProtectionDomain getProtectionDomain(final Class source) {
if (source == null) {
return null;
}
return (ProtectionDomain) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return source.getProtectionDomain();
}
});
}
public static Constructor findConstructor(String desc) {
return findConstructor(desc, defaultLoader);
}
public static Constructor findConstructor(String desc, ClassLoader loader) {
try {
int lparen = desc.indexOf('(');
String className = desc.substring(0, lparen).trim();
return getClass(className, loader).getConstructor(parseTypes(desc, loader));
} catch (ClassNotFoundException ex) {
throw new ReflectException(ex);
} catch (NoSuchMethodException ex) {
throw new ReflectException(ex);
}
}
public static Method findMethod(String desc) {
return findMethod(desc, defaultLoader);
}
public static Method findMethod(String desc, ClassLoader loader) {
try {
int lparen = desc.indexOf('(');
int dot = desc.lastIndexOf('.', lparen);
String className = desc.substring(0, dot).trim();
String methodName = desc.substring(dot + 1, lparen).trim();
return getClass(className, loader).getDeclaredMethod(methodName, parseTypes(desc, loader));
} catch (ClassNotFoundException ex) {
throw new ReflectException(ex);
} catch (NoSuchMethodException ex) {
throw new ReflectException(ex);
}
}
private static Class[] parseTypes(String desc, ClassLoader loader) throws ClassNotFoundException {
int lparen = desc.indexOf('(');
int rparen = desc.indexOf(')', lparen);
List params = new ArrayList();
int start = lparen + 1;
for (;;) {
int comma = desc.indexOf(',', start);
if (comma < 0) {
break;
}
params.add(desc.substring(start, comma).trim());
start = comma + 1;
}
if (start < rparen) {
params.add(desc.substring(start, rparen).trim());
}
Class[] types = new Class[params.size()];
for (int i = 0; i < types.length; i++) {
types[i] = getClass((String) params.get(i), loader);
}
return types;
}
private static Class getClass(String className, ClassLoader loader) throws ClassNotFoundException {
return getClass(className, loader, CGLIB_PACKAGES);
}
private static Class getClass(String className, ClassLoader loader, String[] packages)
throws ClassNotFoundException {
String save = className;
int dimensions = 0;
int index = 0;
while ((index = className.indexOf("[]", index) + 1) > 0) {
dimensions++;
}
StringBuilder brackets = new StringBuilder(className.length() - dimensions);
for (int i = 0; i < dimensions; i++) {
brackets.append('[');
}
className = className.substring(0, className.length() - 2 * dimensions);
String prefix = (dimensions > 0) ? brackets + "L" : "";
String suffix = (dimensions > 0) ? ";" : "";
try {
return Class.forName(prefix + className + suffix, false, loader);
} catch (ClassNotFoundException ignore) {
}
for (int i = 0; i < packages.length; i++) {
try {
return Class.forName(prefix + packages[i] + '.' + className + suffix, false, loader);
} catch (ClassNotFoundException ignore) {
}
}
if (dimensions == 0) {
Class c = (Class) primitives.get(className);
if (c != null) {
return c;
}
} else {
String transform = (String) transforms.get(className);
if (transform != null) {
try {
return Class.forName(brackets + transform, false, loader);
} catch (ClassNotFoundException ignore) {
}
}
}
throw new ClassNotFoundException(save);
}
public static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
public static Object newInstance(Class type) {
return newInstance(type, EMPTY_CLASS_ARRAY, null);
}
public static Object newInstance(Class type, Class[] parameterTypes, Object[] args) {
return newInstance(getConstructor(type, parameterTypes), args);
}
public static Object newInstance(final Constructor cstruct, final Object[] args) {
boolean flag = cstruct.isAccessible();
try {
if (!flag) {
cstruct.setAccessible(true);
}
Object result = cstruct.newInstance(args);
return result;
} catch (InstantiationException e) {
throw new ReflectException(e);
} catch (IllegalAccessException e) {
throw new ReflectException(e);
} catch (InvocationTargetException e) {
throw new ReflectException(e.getTargetException());
} finally {
if (!flag) {
cstruct.setAccessible(flag);
}
}
}
public static Constructor getConstructor(Class type, Class[] parameterTypes) {
try {
Constructor constructor = type.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);
return constructor;
} catch (NoSuchMethodException e) {
throw new ReflectException(e);
}
}
public static String[] getNames(Class[] classes) {
if (classes == null)
return null;
String[] names = new String[classes.length];
for (int i = 0; i < names.length; i++) {
names[i] = classes[i].getName();
}
return names;
}
public static Class[] getClasses(Object[] objects) {
Class[] classes = new Class[objects.length];
for (int i = 0; i < objects.length; i++) {
classes[i] = objects[i].getClass();
}
return classes;
}
public static Method findNewInstance(Class iface) {
Method m = findInterfaceMethod(iface);
if (!m.getName().equals("newInstance")) {
throw new IllegalArgumentException(iface + " missing newInstance method");
}
return m;
}
public static Method[] getPropertyMethods(PropertyDescriptor[] properties, boolean read, boolean write) {
Set methods = new HashSet();
for (int i = 0; i < properties.length; i++) {
PropertyDescriptor pd = properties[i];
if (read) {
methods.add(pd.getReadMethod());
}
if (write) {
methods.add(pd.getWriteMethod());
}
}
methods.remove(null);
return (Method[]) methods.toArray(new Method[methods.size()]);
}
public static PropertyDescriptor[] getBeanProperties(Class type) {
return getPropertiesHelper(type, true, true);
}
public static PropertyDescriptor[] getBeanGetters(Class type) {
return getPropertiesHelper(type, true, false);
}
public static PropertyDescriptor[] getBeanSetters(Class type) {
return getPropertiesHelper(type, false, true);
}
private static PropertyDescriptor[] getPropertiesHelper(Class type, boolean read, boolean write) {
try {
BeanInfo info = Introspector.getBeanInfo(type, Object.class);
PropertyDescriptor[] all = info.getPropertyDescriptors();
if (read && write) {
return all;
}
List properties = new ArrayList(all.length);
for (int i = 0; i < all.length; i++) {
PropertyDescriptor pd = all[i];
if ((read && pd.getReadMethod() != null) || (write && pd.getWriteMethod() != null)) {
properties.add(pd);
}
}
return (PropertyDescriptor[]) properties.toArray(new PropertyDescriptor[properties.size()]);
} catch (IntrospectionException e) {
throw new ReflectException(e);
}
}
public static Method findDeclaredMethod(final Class type, final String methodName, final Class[] parameterTypes)
throws NoSuchMethodException {
Class cl = type;
while (cl != null) {
try {
return cl.getDeclaredMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
cl = cl.getSuperclass();
}
}
throw new NoSuchMethodException(methodName);
}
public static List addAllMethods(final Class type, final List list) {
if (type == Object.class) {
list.addAll(OBJECT_METHODS);
} else
list.addAll(java.util.Arrays.asList(type.getDeclaredMethods()));
Class superclass = type.getSuperclass();
if (superclass != null) {
addAllMethods(superclass, list);
}
Class[] interfaces = type.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
addAllMethods(interfaces[i], list);
}
return list;
}
public static List addAllInterfaces(Class type, List list) {
Class superclass = type.getSuperclass();
if (superclass != null) {
list.addAll(Arrays.asList(type.getInterfaces()));
addAllInterfaces(superclass, list);
}
return list;
}
public static Method findInterfaceMethod(Class iface) {
if (!iface.isInterface()) {
throw new IllegalArgumentException(iface + " is not an interface");
}
Method[] methods = iface.getDeclaredMethods();
if (methods.length != 1) {
throw new IllegalArgumentException("expecting exactly 1 method in " + iface);
}
return methods[0];
}
// SPRING PATCH BEGIN
public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception {
return defineClass(className, b, loader, null, null);
}
public static Class defineClass(String className, byte[] b, ClassLoader loader, ProtectionDomain protectionDomain)
throws Exception {
return defineClass(className, b, loader, protectionDomain, null);
}
public static Class defineClass(String className, byte[] b, ClassLoader loader, ProtectionDomain protectionDomain,
Class<?> contextClass) throws Exception {
Class c = null;
// Preferred option: JDK 9+ Lookup.defineClass API if ClassLoader matches
if (contextClass != null && contextClass.getClassLoader() == loader && privateLookupInMethod != null
&& lookupDefineClassMethod != null) {
try {
MethodHandles.Lookup lookup = (MethodHandles.Lookup) privateLookupInMethod.invoke(null, contextClass,
MethodHandles.lookup());
c = (Class) lookupDefineClassMethod.invoke(lookup, b);
} catch (InvocationTargetException ex) {
Throwable target = ex.getTargetException();
if (target.getClass() != LinkageError.class && target.getClass() != IllegalArgumentException.class) {
throw new ReflectException(target);
}
// in case of plain LinkageError (class already defined)
// or IllegalArgumentException (class in different package):
// fall through to traditional ClassLoader.defineClass below
} catch (Throwable ex) {
throw new ReflectException(ex);
}
}
// Classic option: protected ClassLoader.defineClass method
if (c == null && classLoaderDefineClassMethod != null) {
if (protectionDomain == null) {
protectionDomain = PROTECTION_DOMAIN;
}
Object[] args = new Object[] { className, b, 0, b.length, protectionDomain };
try {
if (!classLoaderDefineClassMethod.isAccessible()) {
classLoaderDefineClassMethod.setAccessible(true);
}
c = (Class) classLoaderDefineClassMethod.invoke(loader, args);
} catch (InvocationTargetException ex) {
throw new ReflectException(ex.getTargetException());
} catch (Throwable ex) {
// Fall through if setAccessible fails with InaccessibleObjectException on JDK
// 9+
// (on the module path and/or with a JVM bootstrapped with
// --illegal-access=deny)
if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
throw new ReflectException(ex);
}
}
}
// Fallback option: JDK 9+ Lookup.defineClass API even if ClassLoader does not
// match
if (c == null && contextClass != null && contextClass.getClassLoader() != loader
&& privateLookupInMethod != null && lookupDefineClassMethod != null) {
try {
MethodHandles.Lookup lookup = (MethodHandles.Lookup) privateLookupInMethod.invoke(null, contextClass,
MethodHandles.lookup());
c = (Class) lookupDefineClassMethod.invoke(lookup, b);
} catch (InvocationTargetException ex) {
throw new ReflectException(ex.getTargetException());
} catch (Throwable ex) {
throw new ReflectException(ex);
}
}
// No defineClass variant available at all?
if (c == null) {
throw new ReflectException(THROWABLE);
}
// Force static initializers to run.
Class.forName(className, true, loader);
return c;
}
// SPRING PATCH END
public static int findPackageProtected(Class[] classes) {
for (int i = 0; i < classes.length; i++) {
if (!Modifier.isPublic(classes[i].getModifiers())) {
return i;
}
}
return 0;
}
}
package com.taobao.arthas.common;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.List;
import java.util.Random;
import javax.net.ServerSocketFactory;
/**
*
* @author hengyunabc 2018-11-07
*
*/
public class SocketUtils {
/**
* The default minimum value for port ranges used when finding an available
* socket port.
*/
public static final int PORT_RANGE_MIN = 1024;
/**
* The default maximum value for port ranges used when finding an available
* socket port.
*/
public static final int PORT_RANGE_MAX = 65535;
private static final Random random = new Random(System.currentTimeMillis());
private SocketUtils() {
}
public static long findTcpListenProcess(int port) {
try {
if (OSUtils.isWindows()) {
String[] command = { "netstat", "-ano", "-p", "TCP" };
List<String> lines = ExecutingCommand.runNative(command);
for (String line : lines) {
if (line.contains("LISTENING")) {
// TCP 0.0.0.0:49168 0.0.0.0:0 LISTENING 476
String[] strings = line.trim().split("\\s+");
if (strings.length == 5) {
if (strings[1].endsWith(":" + port)) {
return Long.parseLong(strings[4]);
}
}
}
}
}
if (OSUtils.isLinux() || OSUtils.isMac()) {
String pid = ExecutingCommand.getFirstAnswer("lsof -t -s TCP:LISTEN -i TCP:" + port);
if (!pid.trim().isEmpty()) {
return Long.parseLong(pid);
}
}
} catch (Throwable e) {
// ignore
}
return -1;
}
public static boolean isTcpPortAvailable(int port) {
try {
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(port, 1,
InetAddress.getByName("localhost"));
serverSocket.close();
return true;
} catch (Exception ex) {
return false;
}
}
/**
* Find an available TCP port randomly selected from the range
* [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
*
* @return an available TCP port number
* @throws IllegalStateException if no available port could be found
*/
public static int findAvailableTcpPort() {
return findAvailableTcpPort(PORT_RANGE_MIN);
}
/**
* Find an available TCP port randomly selected from the range [{@code minPort},
* {@value #PORT_RANGE_MAX}].
*
* @param minPort the minimum port number
* @return an available TCP port number
* @throws IllegalStateException if no available port could be found
*/
public static int findAvailableTcpPort(int minPort) {
return findAvailableTcpPort(minPort, PORT_RANGE_MAX);
}
/**
* Find an available TCP port randomly selected from the range [{@code minPort},
* {@code maxPort}].
*
* @param minPort the minimum port number
* @param maxPort the maximum port number
* @return an available TCP port number
* @throws IllegalStateException if no available port could be found
*/
public static int findAvailableTcpPort(int minPort, int maxPort) {
return findAvailablePort(minPort, maxPort);
}
/**
* Find an available port for this {@code SocketType}, randomly selected from
* the range [{@code minPort}, {@code maxPort}].
*
* @param minPort the minimum port number
* @param maxPort the maximum port number
* @return an available port number for this socket type
* @throws IllegalStateException if no available port could be found
*/
private static int findAvailablePort(int minPort, int maxPort) {
int portRange = maxPort - minPort;
int candidatePort;
int searchCounter = 0;
do {
if (searchCounter > portRange) {
throw new IllegalStateException(
String.format("Could not find an available tcp port in the range [%d, %d] after %d attempts",
minPort, maxPort, searchCounter));
}
candidatePort = findRandomPort(minPort, maxPort);
searchCounter++;
} while (!isTcpPortAvailable(candidatePort));
return candidatePort;
}
/**
* Find a pseudo-random port number within the range [{@code minPort},
* {@code maxPort}].
*
* @param minPort the minimum port number
* @param maxPort the maximum port number
* @return a random port number within the specified range
*/
private static int findRandomPort(int minPort, int maxPort) {
int portRange = maxPort - minPort;
return minPort + random.nextInt(portRange + 1);
}
}
package com.taobao.arthas.common;
/**
*
* @author hengyunabc 2018-11-22
*
*/
public class UsageRender {
private UsageRender() {
}
public static String render(String usage) {
if (AnsiLog.enableColor()) {
StringBuilder sb = new StringBuilder(1024);
String lines[] = usage.split("\\r?\\n");
for (String line : lines) {
if (line.startsWith("Usage: ")) {
sb.append(AnsiLog.green("Usage: "));
sb.append(line.substring("Usage: ".length()));
} else if (!line.startsWith(" ") && line.endsWith(":")) {
sb.append(AnsiLog.green(line));
} else {
sb.append(line);
}
sb.append('\n');
}
return sb.toString();
} else {
return usage;
}
}
}
package com.taobao.arthas.common;
/**
*
* @author hengyunabc 2021-04-27
*
*/
public class VmToolUtils {
private static String libName = null;
static {
if (OSUtils.isMac()) {
libName = "libArthasJniLibrary.dylib";
}
if (OSUtils.isLinux()) {
libName = "libArthasJniLibrary-x64.so";
if (OSUtils.isArm32()) {
libName = "libArthasJniLibrary-arm.so";
} else if (OSUtils.isArm64()) {
libName = "libArthasJniLibrary-aarch64.so";
}
}
if (OSUtils.isWindows()) {
libName = "libArthasJniLibrary-x64.dll";
}
}
public static String detectLibName() {
return libName;
}
}
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you 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.
*/
/*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/licenses/publicdomain
*/
package com.taobao.arthas.common.concurrent;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;
/**
* An alternative weak-key {@link ConcurrentMap} which is similar to
* {@link ConcurrentHashMap}.
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
public final class ConcurrentWeakKeyHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
/*
* The basic strategy is to subdivide the table among Segments,
* each of which itself is a concurrently readable hash table.
*/
/**
* The default initial capacity for this table, used when not otherwise
* specified in a constructor.
*/
static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The default load factor for this table, used when not otherwise specified
* in a constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The default concurrency level for this table, used when not otherwise
* specified in a constructor.
*/
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
/**
* The maximum capacity, used if a higher value is implicitly specified by
* either of the constructors with arguments. MUST be a power of two
* &lt;= 1&lt;&lt;30 to ensure that entries are indexable using integers.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The maximum number of segments to allow; used to bound constructor
* arguments.
*/
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
/**
* Number of unsynchronized retries in size and containsValue methods before
* resorting to locking. This is used to avoid unbounded retries if tables
* undergo continuous modification which would make it impossible to obtain
* an accurate result.
*/
static final int RETRIES_BEFORE_LOCK = 2;
/* ---------------- Fields -------------- */
/**
* Mask value for indexing into segments. The upper bits of a key's hash
* code are used to choose the segment.
*/
final int segmentMask;
/**
* Shift value for indexing within segments.
*/
final int segmentShift;
/**
* The segments, each of which is a specialized hash table
*/
final Segment<K, V>[] segments;
Set<K> keySet;
Set<Map.Entry<K, V>> entrySet;
Collection<V> values;
/* ---------------- Small Utilities -------------- */
/**
* Applies a supplemental hash function to a given hashCode, which defends
* against poor quality hash functions. This is critical because
* ConcurrentReferenceHashMap uses power-of-two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ in lower
* or upper bits.
*/
private static int hash(int h) {
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += h << 15 ^ 0xffffcd7d;
h ^= h >>> 10;
h += h << 3;
h ^= h >>> 6;
h += (h << 2) + (h << 14);
return h ^ h >>> 16;
}
/**
* Returns the segment that should be used for key with given hash.
*
* @param hash the hash code for the key
* @return the segment
*/
Segment<K, V> segmentFor(int hash) {
return segments[hash >>> segmentShift & segmentMask];
}
private static int hashOf(Object key) {
return hash(key.hashCode());
}
/* ---------------- Inner Classes -------------- */
/**
* A weak-key reference which stores the key hash needed for reclamation.
*/
static final class WeakKeyReference<K> extends WeakReference<K> {
final int hash;
WeakKeyReference(K key, int hash, ReferenceQueue<Object> refQueue) {
super(key, refQueue);
this.hash = hash;
}
public int keyHash() {
return hash;
}
public Object keyRef() {
return this;
}
}
/**
* ConcurrentReferenceHashMap list entry. Note that this is never exported
* out as a user-visible Map.Entry.
*
* Because the value field is volatile, not final, it is legal wrt
* the Java Memory Model for an unsynchronized reader to see null
* instead of initial value when read via a data race. Although a
* reordering leading to this is not likely to ever actually
* occur, the Segment.readValueUnderLock method is used as a
* backup in case a null (pre-initialized) value is ever seen in
* an unsynchronized access method.
*/
static final class HashEntry<K, V> {
final Object keyRef;
final int hash;
volatile Object valueRef;
final HashEntry<K, V> next;
HashEntry(
K key, int hash, HashEntry<K, V> next, V value,
ReferenceQueue<Object> refQueue) {
this.hash = hash;
this.next = next;
keyRef = new WeakKeyReference<K>(key, hash, refQueue);
valueRef = value;
}
@SuppressWarnings("unchecked")
K key() {
return ((Reference<K>) keyRef).get();
}
V value() {
return dereferenceValue(valueRef);
}
@SuppressWarnings("unchecked")
V dereferenceValue(Object value) {
if (value instanceof WeakKeyReference) {
return ((Reference<V>) value).get();
}
return (V) value;
}
void setValue(V value) {
valueRef = value;
}
@SuppressWarnings("unchecked")
static <K, V> HashEntry<K, V>[] newArray(int i) {
return new HashEntry[i];
}
}
/**
* Segments are specialized versions of hash tables. This subclasses from
* ReentrantLock opportunistically, just to simplify some locking and avoid
* separate construction.
*/
static final class Segment<K, V> extends ReentrantLock {
/*
* Segments maintain a table of entry lists that are ALWAYS kept in a
* consistent state, so can be read without locking. Next fields of
* nodes are immutable (final). All list additions are performed at the
* front of each bin. This makes it easy to check changes, and also fast
* to traverse. When nodes would otherwise be changed, new nodes are
* created to replace them. This works well for hash tables since the
* bin lists tend to be short. (The average length is less than two for
* the default load factor threshold.)
*
* Read operations can thus proceed without locking, but rely on
* selected uses of volatiles to ensure that completed write operations
* performed by other threads are noticed. For most purposes, the
* "count" field, tracking the number of elements, serves as that
* volatile variable ensuring visibility. This is convenient because
* this field needs to be read in many read operations anyway:
*
* - All (unsynchronized) read operations must first read the
* "count" field, and should not look at table entries if
* it is 0.
*
* - All (synchronized) write operations should write to
* the "count" field after structurally changing any bin.
* The operations must not take any action that could even
* momentarily cause a concurrent read operation to see
* inconsistent data. This is made easier by the nature of
* the read operations in Map. For example, no operation
* can reveal that the table has grown but the threshold
* has not yet been updated, so there are no atomicity
* requirements for this with respect to reads.
*
* As a guide, all critical volatile reads and writes to the count field
* are marked in code comments.
*/
private static final long serialVersionUID = -8328104880676891126L;
/**
* The number of elements in this segment's region.
*/
transient volatile int count;
/**
* Number of updates that alter the size of the table. This is used
* during bulk-read methods to make sure they see a consistent snapshot:
* If modCounts change during a traversal of segments computing size or
* checking containsValue, then we might have an inconsistent view of
* state so (usually) must retry.
*/
int modCount;
/**
* The table is rehashed when its size exceeds this threshold.
* (The value of this field is always <tt>(capacity * loadFactor)</tt>.)
*/
int threshold;
/**
* The per-segment table.
*/
transient volatile HashEntry<K, V>[] table;
/**
* The load factor for the hash table. Even though this value is same
* for all segments, it is replicated to avoid needing links to outer
* object.
*/
final float loadFactor;
/**
* The collected weak-key reference queue for this segment. This should
* be (re)initialized whenever table is assigned,
*/
transient volatile ReferenceQueue<Object> refQueue;
Segment(int initialCapacity, float lf) {
loadFactor = lf;
setTable(HashEntry.<K, V>newArray(initialCapacity));
}
@SuppressWarnings("unchecked")
static <K, V> Segment<K, V>[] newArray(int i) {
return new Segment[i];
}
private static boolean keyEq(Object src, Object dest) {
return src.equals(dest);
}
/**
* Sets table to new HashEntry array. Call only while holding lock or in
* constructor.
*/
void setTable(HashEntry<K, V>[] newTable) {
threshold = (int) (newTable.length * loadFactor);
table = newTable;
refQueue = new ReferenceQueue<Object>();
}
/**
* Returns properly casted first entry of bin for given hash.
*/
HashEntry<K, V> getFirst(int hash) {
HashEntry<K, V>[] tab = table;
return tab[hash & tab.length - 1];
}
HashEntry<K, V> newHashEntry(
K key, int hash, HashEntry<K, V> next, V value) {
return new HashEntry<K, V>(
key, hash, next, value, refQueue);
}
/**
* Reads value field of an entry under lock. Called if value field ever
* appears to be null. This is possible only if a compiler happens to
* reorder a HashEntry initialization with its table assignment, which
* is legal under memory model but is not known to ever occur.
*/
V readValueUnderLock(HashEntry<K, V> e) {
lock();
try {
removeStale();
return e.value();
} finally {
unlock();
}
}
/* Specialized implementations of map methods */
V get(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry<K, V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && keyEq(key, e.key())) {
Object opaque = e.valueRef;
if (opaque != null) {
return e.dereferenceValue(opaque);
}
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;
}
boolean containsKey(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry<K, V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && keyEq(key, e.key())) {
return true;
}
e = e.next;
}
}
return false;
}
boolean containsValue(Object value) {
if (count != 0) { // read-volatile
for (HashEntry<K, V> e: table) {
for (; e != null; e = e.next) {
Object opaque = e.valueRef;
V v;
if (opaque == null) {
v = readValueUnderLock(e); // recheck
} else {
v = e.dereferenceValue(opaque);
}
if (value.equals(v)) {
return true;
}
}
}
}
return false;
}
boolean replace(K key, int hash, V oldValue, V newValue) {
lock();
try {
removeStale();
HashEntry<K, V> e = getFirst(hash);
while (e != null && (e.hash != hash || !keyEq(key, e.key()))) {
e = e.next;
}
boolean replaced = false;
if (e != null && oldValue.equals(e.value())) {
replaced = true;
e.setValue(newValue);
}
return replaced;
} finally {
unlock();
}
}
V replace(K key, int hash, V newValue) {
lock();
try {
removeStale();
HashEntry<K, V> e = getFirst(hash);
while (e != null && (e.hash != hash || !keyEq(key, e.key()))) {
e = e.next;
}
V oldValue = null;
if (e != null) {
oldValue = e.value();
e.setValue(newValue);
}
return oldValue;
} finally {
unlock();
}
}
V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
removeStale();
int c = count;
if (c ++ > threshold) { // ensure capacity
int reduced = rehash();
if (reduced > 0) {
count = (c -= reduced) - 1; // write-volatile
}
}
HashEntry<K, V>[] tab = table;
int index = hash & tab.length - 1;
HashEntry<K, V> first = tab[index];
HashEntry<K, V> e = first;
while (e != null && (e.hash != hash || !keyEq(key, e.key()))) {
e = e.next;
}
V oldValue;
if (e != null) {
oldValue = e.value();
if (!onlyIfAbsent) {
e.setValue(value);
}
} else {
oldValue = null;
++ modCount;
tab[index] = newHashEntry(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock();
}
}
int rehash() {
HashEntry<K, V>[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity >= MAXIMUM_CAPACITY) {
return 0;
}
/*
* Reclassify nodes in each list to new Map. Because we are using
* power-of-two expansion, the elements from each bin must either
* stay at same index, or move with a power of two offset. We
* eliminate unnecessary node creation by catching cases where old
* nodes can be reused because their next fields won't change.
* Statistically, at the default threshold, only about one-sixth of
* them need cloning when a table doubles. The nodes they replace
* will be garbage collectable as soon as they are no longer
* referenced by any reader thread that may be in the midst of
* traversing table right now.
*/
HashEntry<K, V>[] newTable = HashEntry.newArray(oldCapacity << 1);
threshold = (int) (newTable.length * loadFactor);
int sizeMask = newTable.length - 1;
int reduce = 0;
for (HashEntry<K, V> e: oldTable) {
// We need to guarantee that any existing reads of old Map can
// proceed. So we cannot yet null out each bin.
if (e != null) {
HashEntry<K, V> next = e.next;
int idx = e.hash & sizeMask;
// Single node on list
if (next == null) {
newTable[idx] = e;
} else {
// Reuse trailing consecutive sequence at same slot
HashEntry<K, V> lastRun = e;
int lastIdx = idx;
for (HashEntry<K, V> last = next; last != null; last = last.next) {
int k = last.hash & sizeMask;
if (k != lastIdx) {
lastIdx = k;
lastRun = last;
}
}
newTable[lastIdx] = lastRun;
// Clone all remaining nodes
for (HashEntry<K, V> p = e; p != lastRun; p = p.next) {
// Skip GC'd weak references
K key = p.key();
if (key == null) {
reduce++;
continue;
}
int k = p.hash & sizeMask;
HashEntry<K, V> n = newTable[k];
newTable[k] = newHashEntry(key, p.hash, n, p.value());
}
}
}
}
table = newTable;
return reduce;
}
/**
* Remove; match on key only if value null, else match both.
*/
V remove(Object key, int hash, Object value, boolean refRemove) {
lock();
try {
if (!refRemove) {
removeStale();
}
int c = count - 1;
HashEntry<K, V>[] tab = table;
int index = hash & tab.length - 1;
HashEntry<K, V> first = tab[index];
HashEntry<K, V> e = first;
// a reference remove operation compares the Reference instance
while (e != null && key != e.keyRef &&
(refRemove || hash != e.hash || !keyEq(key, e.key()))) {
e = e.next;
}
V oldValue = null;
if (e != null) {
V v = e.value();
if (value == null || value.equals(v)) {
oldValue = v;
// All entries following removed node can stay in list,
// but all preceding ones need to be cloned.
++ modCount;
HashEntry<K, V> newFirst = e.next;
for (HashEntry<K, V> p = first; p != e; p = p.next) {
K pKey = p.key();
if (pKey == null) { // Skip GC'd keys
c --;
continue;
}
newFirst = newHashEntry(
pKey, p.hash, newFirst, p.value());
}
tab[index] = newFirst;
count = c; // write-volatile
}
}
return oldValue;
} finally {
unlock();
}
}
@SuppressWarnings("rawtypes")
void removeStale() {
WeakKeyReference ref;
while ((ref = (WeakKeyReference) refQueue.poll()) != null) {
remove(ref.keyRef(), ref.keyHash(), null, true);
}
}
void clear() {
if (count != 0) {
lock();
try {
Arrays.fill(table, null);
++ modCount;
// replace the reference queue to avoid unnecessary stale
// cleanups
refQueue = new ReferenceQueue<Object>();
count = 0; // write-volatile
} finally {
unlock();
}
}
}
}
/* ---------------- Public operations -------------- */
/**
* Creates a new, empty map with the specified initial capacity, load factor
* and concurrency level.
*
* @param initialCapacity the initial capacity. The implementation performs
* internal sizing to accommodate this many elements.
* @param loadFactor the load factor threshold, used to control resizing.
* Resizing may be performed when the average number of
* elements per bin exceeds this threshold.
* @param concurrencyLevel the estimated number of concurrently updating
* threads. The implementation performs internal
* sizing to try to accommodate this many threads.
* @throws IllegalArgumentException if the initial capacity is negative or
* the load factor or concurrencyLevel are
* nonpositive.
*/
public ConcurrentWeakKeyHashMap(
int initialCapacity, float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) {
throw new IllegalArgumentException();
}
if (concurrencyLevel > MAX_SEGMENTS) {
concurrencyLevel = MAX_SEGMENTS;
}
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++ sshift;
ssize <<= 1;
}
segmentShift = 32 - sshift;
segmentMask = ssize - 1;
segments = Segment.newArray(ssize);
if (initialCapacity > MAXIMUM_CAPACITY) {
initialCapacity = MAXIMUM_CAPACITY;
}
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity) {
++ c;
}
int cap = 1;
while (cap < c) {
cap <<= 1;
}
for (int i = 0; i < segments.length; ++ i) {
segments[i] = new Segment<K, V>(cap, loadFactor);
}
}
/**
* Creates a new, empty map with the specified initial capacity and load
* factor and with the default reference types (weak keys, strong values),
* and concurrencyLevel (16).
*
* @param initialCapacity The implementation performs internal sizing to
* accommodate this many elements.
* @param loadFactor the load factor threshold, used to control resizing.
* Resizing may be performed when the average number of
* elements per bin exceeds this threshold.
* @throws IllegalArgumentException if the initial capacity of elements is
* negative or the load factor is
* nonpositive
*/
public ConcurrentWeakKeyHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);
}
/**
* Creates a new, empty map with the specified initial capacity, and with
* default reference types (weak keys, strong values), load factor (0.75)
* and concurrencyLevel (16).
*
* @param initialCapacity the initial capacity. The implementation performs
* internal sizing to accommodate this many elements.
* @throws IllegalArgumentException if the initial capacity of elements is
* negative.
*/
public ConcurrentWeakKeyHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
/**
* Creates a new, empty map with a default initial capacity (16), reference
* types (weak keys, strong values), default load factor (0.75) and
* concurrencyLevel (16).
*/
public ConcurrentWeakKeyHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
/**
* Creates a new map with the same mappings as the given map. The map is
* created with a capacity of 1.5 times the number of mappings in the given
* map or 16 (whichever is greater), and a default load factor (0.75) and
* concurrencyLevel (16).
*
* @param m the map
*/
public ConcurrentWeakKeyHashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR,
DEFAULT_CONCURRENCY_LEVEL);
putAll(m);
}
/**
* Returns <tt>true</tt> if this map contains no key-value mappings.
*
* @return <tt>true</tt> if this map contains no key-value mappings
*/
@Override
public boolean isEmpty() {
final Segment<K, V>[] segments = this.segments;
/*
* We keep track of per-segment modCounts to avoid ABA problems in which
* an element in one segment was added and in another removed during
* traversal, in which case the table was never actually empty at any
* point. Note the similar use of modCounts in the size() and
* containsValue() methods, which are the only other methods also
* susceptible to ABA problems.
*/
int[] mc = new int[segments.length];
int mcsum = 0;
for (int i = 0; i < segments.length; ++ i) {
if (segments[i].count != 0) {
return false;
} else {
mcsum += mc[i] = segments[i].modCount;
}
}
// If mcsum happens to be zero, then we know we got a snapshot before
// any modifications at all were made. This is probably common enough
// to bother tracking.
if (mcsum != 0) {
for (int i = 0; i < segments.length; ++ i) {
if (segments[i].count != 0 || mc[i] != segments[i].modCount) {
return false;
}
}
}
return true;
}
/**
* Returns the number of key-value mappings in this map. If the map contains
* more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
*
* @return the number of key-value mappings in this map
*/
@Override
public int size() {
final Segment<K, V>[] segments = this.segments;
long sum = 0;
long check = 0;
int[] mc = new int[segments.length];
// Try a few times to get accurate count. On failure due to continuous
// async changes in table, resort to locking.
for (int k = 0; k < RETRIES_BEFORE_LOCK; ++ k) {
check = 0;
sum = 0;
int mcsum = 0;
for (int i = 0; i < segments.length; ++ i) {
sum += segments[i].count;
mcsum += mc[i] = segments[i].modCount;
}
if (mcsum != 0) {
for (int i = 0; i < segments.length; ++ i) {
check += segments[i].count;
if (mc[i] != segments[i].modCount) {
check = -1; // force retry
break;
}
}
}
if (check == sum) {
break;
}
}
if (check != sum) { // Resort to locking all segments
sum = 0;
for (Segment<K, V> segment: segments) {
segment.lock();
}
for (Segment<K, V> segment: segments) {
sum += segment.count;
}
for (Segment<K, V> segment: segments) {
segment.unlock();
}
}
if (sum > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else {
return (int) sum;
}
}
/**
* Returns the value to which the specified key is mapped, or {@code null}
* if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key {@code k} to
* a value {@code v} such that {@code key.equals(k)}, then this method
* returns {@code v}; otherwise it returns {@code null}. (There can be at
* most one such mapping.)
*
* @throws NullPointerException if the specified key is null
*/
@Override
public V get(Object key) {
int hash = hashOf(key);
return segmentFor(hash).get(key, hash);
}
/**
* Tests if the specified object is a key in this table.
*
* @param key possible key
* @return <tt>true</tt> if and only if the specified object is a key in
* this table, as determined by the <tt>equals</tt> method;
* <tt>false</tt> otherwise.
* @throws NullPointerException if the specified key is null
*/
@Override
public boolean containsKey(Object key) {
int hash = hashOf(key);
return segmentFor(hash).containsKey(key, hash);
}
/**
* Returns <tt>true</tt> if this map maps one or more keys to the specified
* value. Note: This method requires a full internal traversal of the hash
* table, and so is much slower than method <tt>containsKey</tt>.
*
* @param value value whose presence in this map is to be tested
* @return <tt>true</tt> if this map maps one or more keys to the specified
* value
* @throws NullPointerException if the specified value is null
*/
@Override
public boolean containsValue(Object value) {
if (value == null) {
throw new NullPointerException();
}
// See explanation of modCount use above
final Segment<K, V>[] segments = this.segments;
int[] mc = new int[segments.length];
// Try a few times without locking
for (int k = 0; k < RETRIES_BEFORE_LOCK; ++ k) {
int mcsum = 0;
for (int i = 0; i < segments.length; ++ i) {
mcsum += mc[i] = segments[i].modCount;
if (segments[i].containsValue(value)) {
return true;
}
}
boolean cleanSweep = true;
if (mcsum != 0) {
for (int i = 0; i < segments.length; ++ i) {
if (mc[i] != segments[i].modCount) {
cleanSweep = false;
break;
}
}
}
if (cleanSweep) {
return false;
}
}
// Resort to locking all segments
for (Segment<K, V> segment: segments) {
segment.lock();
}
boolean found = false;
try {
for (Segment<K, V> segment: segments) {
if (segment.containsValue(value)) {
found = true;
break;
}
}
} finally {
for (Segment<K, V> segment: segments) {
segment.unlock();
}
}
return found;
}
/**
* Legacy method testing if some key maps into the specified value in this
* table. This method is identical in functionality to
* {@link #containsValue}, and exists solely to ensure full compatibility
* with class {@link Hashtable}, which supported this method prior to
* introduction of the Java Collections framework.
*
* @param value a value to search for
* @return <tt>true</tt> if and only if some key maps to the <tt>value</tt>
* argument in this table as determined by the <tt>equals</tt>
* method; <tt>false</tt> otherwise
* @throws NullPointerException if the specified value is null
*/
public boolean contains(Object value) {
return containsValue(value);
}
/**
* Maps the specified key to the specified value in this table. Neither the
* key nor the value can be null.
*
* <p>The value can be retrieved by calling the <tt>get</tt> method with a
* key that is equal to the original key.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or <tt>null</tt>
* if there was no mapping for <tt>key</tt>
* @throws NullPointerException if the specified key or value is null
*/
@Override
public V put(K key, V value) {
if (value == null) {
throw new NullPointerException();
}
int hash = hashOf(key);
return segmentFor(hash).put(key, hash, value, false);
}
/**
* @return the previous value associated with the specified key, or
* <tt>null</tt> if there was no mapping for the key
* @throws NullPointerException if the specified key or value is null
*/
public V putIfAbsent(K key, V value) {
if (value == null) {
throw new NullPointerException();
}
int hash = hashOf(key);
return segmentFor(hash).put(key, hash, value, true);
}
/**
* Copies all of the mappings from the specified map to this one. These
* mappings replace any mappings that this map had for any of the keys
* currently in the specified map.
*
* @param m mappings to be stored in this map
*/
@Override
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e: m.entrySet()) {
put(e.getKey(), e.getValue());
}
}
/**
* Removes the key (and its corresponding value) from this map. This method
* does nothing if the key is not in the map.
*
* @param key the key that needs to be removed
* @return the previous value associated with <tt>key</tt>, or <tt>null</tt>
* if there was no mapping for <tt>key</tt>
* @throws NullPointerException if the specified key is null
*/
@Override
public V remove(Object key) {
int hash = hashOf(key);
return segmentFor(hash).remove(key, hash, null, false);
}
/**
* @throws NullPointerException if the specified key is null
*/
public boolean remove(Object key, Object value) {
int hash = hashOf(key);
if (value == null) {
return false;
}
return segmentFor(hash).remove(key, hash, value, false) != null;
}
/**
* @throws NullPointerException if any of the arguments are null
*/
public boolean replace(K key, V oldValue, V newValue) {
if (oldValue == null || newValue == null) {
throw new NullPointerException();
}
int hash = hashOf(key);
return segmentFor(hash).replace(key, hash, oldValue, newValue);
}
/**
* @return the previous value associated with the specified key, or
* <tt>null</tt> if there was no mapping for the key
* @throws NullPointerException if the specified key or value is null
*/
public V replace(K key, V value) {
if (value == null) {
throw new NullPointerException();
}
int hash = hashOf(key);
return segmentFor(hash).replace(key, hash, value);
}
/**
* Removes all of the mappings from this map.
*/
@Override
public void clear() {
for (Segment<K, V> segment: segments) {
segment.clear();
}
}
/**
* Removes any stale entries whose keys have been finalized. Use of this
* method is normally not necessary since stale entries are automatically
* removed lazily, when blocking operations are required. However, there are
* some cases where this operation should be performed eagerly, such as
* cleaning up old references to a ClassLoader in a multi-classloader
* environment.
*
* Note: this method will acquire locks, one at a time, across all segments
* of this table, so if it is to be used, it should be used sparingly.
*/
public void purgeStaleEntries() {
for (Segment<K, V> segment: segments) {
segment.removeStale();
}
}
/**
* Returns a {@link Set} view of the keys contained in this map. The set is
* backed by the map, so changes to the map are reflected in the set, and
* vice-versa. The set supports element removal, which removes the
* corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
* <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
* <tt>clear</tt> operations. It does not support the <tt>add</tt> or
* <tt>addAll</tt> operations.
*
* <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator that
* will never throw {@link ConcurrentModificationException}, and guarantees
* to traverse elements as they existed upon construction of the iterator,
* and may (but is not guaranteed to) reflect any modifications subsequent
* to construction.
*/
@Override
public Set<K> keySet() {
Set<K> ks = keySet;
return ks != null? ks : (keySet = new KeySet());
}
/**
* Returns a {@link Collection} view of the values contained in this map.
* The collection is backed by the map, so changes to the map are reflected
* in the collection, and vice-versa. The collection supports element
* removal, which removes the corresponding mapping from this map, via the
* <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>, <tt>removeAll</tt>,
* <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not support
* the <tt>add</tt> or <tt>addAll</tt> operations.
*
* <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator that
* will never throw {@link ConcurrentModificationException}, and guarantees
* to traverse elements as they existed upon construction of the iterator,
* and may (but is not guaranteed to) reflect any modifications subsequent
* to construction.
*/
@Override
public Collection<V> values() {
Collection<V> vs = values;
return vs != null? vs : (values = new Values());
}
/**
* Returns a {@link Set} view of the mappings contained in this map.
* The set is backed by the map, so changes to the map are reflected in the
* set, and vice-versa. The set supports element removal, which removes the
* corresponding mapping from the map, via the <tt>Iterator.remove</tt>,
* <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
* <tt>clear</tt> operations. It does not support the <tt>add</tt> or
* <tt>addAll</tt> operations.
*
* <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator that
* will never throw {@link ConcurrentModificationException}, and guarantees
* to traverse elements as they existed upon construction of the iterator,
* and may (but is not guaranteed to) reflect any modifications subsequent
* to construction.
*/
@Override
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> es = entrySet;
return es != null? es : (entrySet = new EntrySet());
}
/**
* Returns an enumeration of the keys in this table.
*
* @return an enumeration of the keys in this table
* @see #keySet()
*/
public Enumeration<K> keys() {
return new KeyIterator();
}
/**
* Returns an enumeration of the values in this table.
*
* @return an enumeration of the values in this table
* @see #values()
*/
public Enumeration<V> elements() {
return new ValueIterator();
}
/* ---------------- Iterator Support -------------- */
abstract class HashIterator {
int nextSegmentIndex;
int nextTableIndex;
HashEntry<K, V>[] currentTable;
HashEntry<K, V> nextEntry;
HashEntry<K, V> lastReturned;
K currentKey; // Strong reference to weak key (prevents gc)
HashIterator() {
nextSegmentIndex = segments.length - 1;
nextTableIndex = -1;
advance();
}
public void rewind() {
nextSegmentIndex = segments.length - 1;
nextTableIndex = -1;
currentTable = null;
nextEntry = null;
lastReturned = null;
currentKey = null;
advance();
}
public boolean hasMoreElements() {
return hasNext();
}
final void advance() {
if (nextEntry != null && (nextEntry = nextEntry.next) != null) {
return;
}
while (nextTableIndex >= 0) {
if ((nextEntry = currentTable[nextTableIndex --]) != null) {
return;
}
}
while (nextSegmentIndex >= 0) {
Segment<K, V> seg = segments[nextSegmentIndex --];
if (seg.count != 0) {
currentTable = seg.table;
for (int j = currentTable.length - 1; j >= 0; -- j) {
if ((nextEntry = currentTable[j]) != null) {
nextTableIndex = j - 1;
return;
}
}
}
}
}
public boolean hasNext() {
while (nextEntry != null) {
if (nextEntry.key() != null) {
return true;
}
advance();
}
return false;
}
HashEntry<K, V> nextEntry() {
do {
if (nextEntry == null) {
throw new NoSuchElementException();
}
lastReturned = nextEntry;
currentKey = lastReturned.key();
advance();
} while (currentKey == null); // Skip GC'd keys
return lastReturned;
}
public void remove() {
if (lastReturned == null) {
throw new IllegalStateException();
}
ConcurrentWeakKeyHashMap.this.remove(currentKey);
lastReturned = null;
}
}
final class KeyIterator
extends HashIterator implements ReusableIterator<K>, Enumeration<K> {
public K next() {
return nextEntry().key();
}
public K nextElement() {
return nextEntry().key();
}
}
final class ValueIterator
extends HashIterator implements ReusableIterator<V>, Enumeration<V> {
public V next() {
return nextEntry().value();
}
public V nextElement() {
return nextEntry().value();
}
}
/*
* This class is needed for JDK5 compatibility.
*/
static class SimpleEntry<K, V> implements Entry<K, V> {
private final K key;
private V value;
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
public SimpleEntry(Entry<? extends K, ? extends V> entry) {
key = entry.getKey();
value = entry.getValue();
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Map.Entry<?, ?>)) {
return false;
}
@SuppressWarnings("rawtypes")
Map.Entry e = (Map.Entry) o;
return eq(key, e.getKey()) && eq(value, e.getValue());
}
@Override
public int hashCode() {
return (key == null? 0 : key.hashCode()) ^ (value == null? 0 : value.hashCode());
}
@Override
public String toString() {
return key + "=" + value;
}
private static boolean eq(Object o1, Object o2) {
return o1 == null? o2 == null : o1.equals(o2);
}
}
/**
* Custom Entry class used by EntryIterator.next(), that relays setValue
* changes to the underlying map.
*/
final class WriteThroughEntry extends SimpleEntry<K, V> {
WriteThroughEntry(K k, V v) {
super(k, v);
}
/**
* Set our entry's value and write through to the map. The value to
* return is somewhat arbitrary here. Since a WriteThroughEntry does not
* necessarily track asynchronous changes, the most recent "previous"
* value could be different from what we return (or could even have been
* removed in which case the put will re-establish). We do not and can
* not guarantee more.
*/
@Override
public V setValue(V value) {
if (value == null) {
throw new NullPointerException();
}
V v = super.setValue(value);
put(getKey(), value);
return v;
}
}
final class EntryIterator extends HashIterator implements
ReusableIterator<Entry<K, V>> {
public Map.Entry<K, V> next() {
HashEntry<K, V> e = nextEntry();
return new WriteThroughEntry(e.key(), e.value());
}
}
final class KeySet extends AbstractSet<K> {
@Override
public Iterator<K> iterator() {
return new KeyIterator();
}
@Override
public int size() {
return ConcurrentWeakKeyHashMap.this.size();
}
@Override
public boolean isEmpty() {
return ConcurrentWeakKeyHashMap.this.isEmpty();
}
@Override
public boolean contains(Object o) {
return containsKey(o);
}
@Override
public boolean remove(Object o) {
return ConcurrentWeakKeyHashMap.this.remove(o) != null;
}
@Override
public void clear() {
ConcurrentWeakKeyHashMap.this.clear();
}
}
final class Values extends AbstractCollection<V> {
@Override
public Iterator<V> iterator() {
return new ValueIterator();
}
@Override
public int size() {
return ConcurrentWeakKeyHashMap.this.size();
}
@Override
public boolean isEmpty() {
return ConcurrentWeakKeyHashMap.this.isEmpty();
}
@Override
public boolean contains(Object o) {
return containsValue(o);
}
@Override
public void clear() {
ConcurrentWeakKeyHashMap.this.clear();
}
}
final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
@Override
public Iterator<Map.Entry<K, V>> iterator() {
return new EntryIterator();
}
@Override
public boolean contains(Object o) {
if (!(o instanceof Map.Entry<?, ?>)) {
return false;
}
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
V v = get(e.getKey());
return v != null && v.equals(e.getValue());
}
@Override
public boolean remove(Object o) {
if (!(o instanceof Map.Entry<?, ?>)) {
return false;
}
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
return ConcurrentWeakKeyHashMap.this.remove(e.getKey(), e.getValue());
}
@Override
public int size() {
return ConcurrentWeakKeyHashMap.this.size();
}
@Override
public boolean isEmpty() {
return ConcurrentWeakKeyHashMap.this.isEmpty();
}
@Override
public void clear() {
ConcurrentWeakKeyHashMap.this.clear();
}
}
}
\ No newline at end of file
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you 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.taobao.arthas.common.concurrent;
import java.util.Iterator;
public interface ReusableIterator<E> extends Iterator<E> {
void rewind();
}
\ No newline at end of file
#Generated by Git-Commit-Id-Plugin
git.build.version=3.6.4
git.closest.tag.commit.count=
git.closest.tag.name=
git.commit.id=35f73a652cf4efc30cc57df74dbab7d5c1de757a
git.commit.id.abbrev=35f73a6
git.commit.id.describe=35f73a6
git.commit.id.describe-short=35f73a6
git.commit.message.full=ci-set-3
git.commit.message.short=ci-set-3
git.commit.user.email=shengnan.hu@ustchcs.com
git.commit.user.name=shengnan.hu
git.dirty=false
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment