All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javaemul.internal.ValueType Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020 Google Inc.
 *
 * 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
 *
 *     https://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 javaemul.internal;

import java.util.Arrays;
import java.util.StringJoiner;
import javaemul.internal.annotations.DoNotAutobox;
import jsinterop.annotations.JsMethod;
import jsinterop.annotations.JsPackage;

/**
 * A base type that provides 'value' type semantics for equals/hashcode/toString via reflection.
 *
 * 

Note that even this is base type, it is not necessarily need to used in an 'extends' clause; * instead it can be used via {@code ValueType.mixin}) helper as well. * *

Note that for the properties that is never read by the app this may change the behavior * compared to handwritten versions. Such that instances would considered equal even if never read * properties are set to different values. */ public abstract class ValueType { @Override public boolean equals(Object o) { return equals(this, o); } @Override public int hashCode() { return hashCode(this); } @Override public String toString() { return toString(this); } // Package private to ensure clinit is run when called from types that use this class as a mixin. static boolean equals(ValueType thisObject, Object o) { if (o == null || o.getClass() != thisObject.getClass()) { return false; } ValueType thatObject = JsUtils.uncheckedCast(o); String[] thisKeys = filteredkeys(thisObject); // Number of properties should match. This might not be true for a generic object comparator; // (e.g. property set to 'null' or 'undefined' might be considered as non-existent for // comparison) however based on our code generation, the properties will be always set for value // type hence this should never happen. if (thisKeys.length != filteredkeys(thatObject).length) { return false; } for (String p : thisKeys) { Object p1 = JsUtils.getProperty(thisObject, p); Object p2 = JsUtils.getProperty(thatObject, p); if (!isAutoValueEqual(p1, p2)) { return false; } } return true; } private static boolean isAutoValueEqual(Object a, Object b) { if (a == b) { return true; } if (a == null || b == null) { return false; } if (ArrayStamper.isStamped(a) && ArrayStamper.isStamped(b)) { // AutoValue only supports primitive arrays so if the array is stamped we can safely assume // this is a primitive array. So here we just check that they have same types and compare the // contents. return a.getClass() == b.getClass() && Arrays.equals(JsUtils.uncheckedCast(a), JsUtils.uncheckedCast(b)); } return a.equals(b); } // Package private to ensure clinit is run when called from types that use this class as a mixin. static int hashCode(ValueType thisObject) { int hashCode = 1; for (String key : filteredkeys(thisObject)) { Object value = JsUtils.getProperty(thisObject, key); if (value == null) { continue; } // AutoValue supports primitives arrays, that needs special handling for hashCode. int h = ArrayStamper.isStamped(value) ? Arrays.hashCode(JsUtils.uncheckedCast(value)) : value.hashCode(); hashCode = (1000003 * hashCode) ^ h; } return hashCode; } // Package private to ensure clinit is run when called from types that use this class as a mixin. static String toString(ValueType thisObject) { String className = thisObject.getClass().getSimpleName(); // Remove AutoValue_ prefix used by the APT if it exists. className = className.substring(className.lastIndexOf("AutoValue_") + 1); StringJoiner joiner = new StringJoiner(", ", className + "{", "}"); for (String p : filteredkeys(thisObject)) { Object value = JsUtils.getProperty(thisObject, p); if (ArrayHelper.isArray(value)) { // Relying on JS toString behavior avoids pulling in extra deps through Arrays.toString. value = "[" + value + "]"; } // Best effort to strip down of mangled names. p = cleanMangledPropertyName(p); joiner.add(p + "=" + value); } return joiner.toString(); } private static boolean COMPILED = "true".equals(System.getProperty("COMPILED", "false")); private static String cleanMangledPropertyName(String name) { if (COMPILED) { // There is no point in the trying to clean up for compiled mode add to the code size since // names are usually obfuscated. return name; } // In the absense of minification, propery name is followed by __ int index = name.startsWith("f_") ? name.indexOf("__", 2) : -1; if (index != -1) { return name.substring(2, index); } // In the case of minification, the propery name will follow with _$ index = name.lastIndexOf("_$"); return index == -1 ? name : name.substring(0, index); } @JsMethod private static native String[] filteredkeys(ValueType type); @JsMethod private static native void mixin( Constructor target, Constructor source, int mask, String... excludes); @JsMethod(namespace = "goog.reflect") static native String objectProperty(String name, Object type); @JsMethod(name = "$J2CL_PRESERVE$", namespace = JsPackage.GLOBAL) static native void preserve(@DoNotAutobox Object... type); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy