Path: blob/master/src/java.management/share/classes/javax/management/ImmutableDescriptor.java
41155 views
/*1* Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.management;2627import com.sun.jmx.mbeanserver.Util;28import java.io.InvalidObjectException;29import java.lang.reflect.Array;30import java.util.Arrays;31import java.util.Comparator;32import java.util.Map;33import java.util.SortedMap;34import java.util.TreeMap;3536/**37* An immutable descriptor.38* @since 1.639*/40public class ImmutableDescriptor implements Descriptor {41private static final long serialVersionUID = 8853308591080540165L;4243/**44* The names of the fields in this ImmutableDescriptor with their45* original case. The names must be in alphabetical order as determined46* by {@link String#CASE_INSENSITIVE_ORDER}.47*/48private final String[] names;49/**50* The values of the fields in this ImmutableDescriptor. The51* elements in this array match the corresponding elements in the52* {@code names} array.53*/54@SuppressWarnings("serial") // Conditionally serializable55private final Object[] values;5657private transient int hashCode = -1;5859/**60* An empty descriptor.61*/62public static final ImmutableDescriptor EMPTY_DESCRIPTOR =63new ImmutableDescriptor();6465/**66* Construct a descriptor containing the given fields and values.67*68* @param fieldNames the field names69* @param fieldValues the field values70* @throws IllegalArgumentException if either array is null, or71* if the arrays have different sizes, or72* if a field name is null or empty, or if the same field name73* appears more than once.74*/75public ImmutableDescriptor(String[] fieldNames, Object[] fieldValues) {76this(makeMap(fieldNames, fieldValues));77}7879/**80* Construct a descriptor containing the given fields. Each String81* must be of the form {@code fieldName=fieldValue}. The field name82* ends at the first {@code =} character; for example if the String83* is {@code a=b=c} then the field name is {@code a} and its value84* is {@code b=c}.85*86* @param fields the field names87* @throws IllegalArgumentException if the parameter is null, or88* if a field name is empty, or if the same field name appears89* more than once, or if one of the strings does not contain90* an {@code =} character.91*/92public ImmutableDescriptor(String... fields) {93this(makeMap(fields));94}9596/**97* <p>Construct a descriptor where the names and values of the fields98* are the keys and values of the given Map.</p>99*100* @param fields the field names and values101* @throws IllegalArgumentException if the parameter is null, or102* if a field name is null or empty, or if the same field name appears103* more than once (which can happen because field names are not case104* sensitive).105*/106public ImmutableDescriptor(Map<String, ?> fields) {107if (fields == null)108throw new IllegalArgumentException("Null Map");109SortedMap<String, Object> map =110new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER);111for (Map.Entry<String, ?> entry : fields.entrySet()) {112String name = entry.getKey();113if (name == null || name.isEmpty())114throw new IllegalArgumentException("Empty or null field name");115if (map.containsKey(name))116throw new IllegalArgumentException("Duplicate name: " + name);117map.put(name, entry.getValue());118}119int size = map.size();120this.names = map.keySet().toArray(new String[size]);121this.values = map.values().toArray(new Object[size]);122}123124/**125* This method can replace a deserialized instance of this126* class with another instance. For example, it might replace127* a deserialized empty ImmutableDescriptor with128* {@link #EMPTY_DESCRIPTOR}.129*130* @return the replacement object, which may be {@code this}.131*132* @throws InvalidObjectException if the read object has invalid fields.133*/134private Object readResolve() throws InvalidObjectException {135136boolean bad = false;137if (names == null || values == null || names.length != values.length)138bad = true;139if (!bad) {140if (names.length == 0 && getClass() == ImmutableDescriptor.class)141return EMPTY_DESCRIPTOR;142final Comparator<String> compare = String.CASE_INSENSITIVE_ORDER;143String lastName = ""; // also catches illegal null name144for (int i = 0; i < names.length; i++) {145if (names[i] == null ||146compare.compare(lastName, names[i]) >= 0) {147bad = true;148break;149}150lastName = names[i];151}152}153if (bad)154throw new InvalidObjectException("Bad names or values");155156return this;157}158159private static SortedMap<String, ?> makeMap(String[] fieldNames,160Object[] fieldValues) {161if (fieldNames == null || fieldValues == null)162throw new IllegalArgumentException("Null array parameter");163if (fieldNames.length != fieldValues.length)164throw new IllegalArgumentException("Different size arrays");165SortedMap<String, Object> map =166new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER);167for (int i = 0; i < fieldNames.length; i++) {168String name = fieldNames[i];169if (name == null || name.isEmpty())170throw new IllegalArgumentException("Empty or null field name");171Object old = map.put(name, fieldValues[i]);172if (old != null) {173throw new IllegalArgumentException("Duplicate field name: " +174name);175}176}177return map;178}179180private static SortedMap<String, ?> makeMap(String[] fields) {181if (fields == null)182throw new IllegalArgumentException("Null fields parameter");183String[] fieldNames = new String[fields.length];184String[] fieldValues = new String[fields.length];185for (int i = 0; i < fields.length; i++) {186String field = fields[i];187int eq = field.indexOf('=');188if (eq < 0) {189throw new IllegalArgumentException("Missing = character: " +190field);191}192fieldNames[i] = field.substring(0, eq);193// makeMap will catch the case where the name is empty194fieldValues[i] = field.substring(eq + 1);195}196return makeMap(fieldNames, fieldValues);197}198199/**200* <p>Return an {@code ImmutableDescriptor} whose contents are the union of201* the given descriptors. Every field name that appears in any of202* the descriptors will appear in the result with the203* value that it has when the method is called. Subsequent changes204* to any of the descriptors do not affect the ImmutableDescriptor205* returned here.</p>206*207* <p>In the simplest case, there is only one descriptor and the208* returned {@code ImmutableDescriptor} is a copy of its fields at the209* time this method is called:</p>210*211* <pre>212* Descriptor d = something();213* ImmutableDescriptor copy = ImmutableDescriptor.union(d);214* </pre>215*216* @param descriptors the descriptors to be combined. Any of the217* descriptors can be null, in which case it is skipped.218*219* @return an {@code ImmutableDescriptor} that is the union of the given220* descriptors. The returned object may be identical to one of the221* input descriptors if it is an ImmutableDescriptor that contains all of222* the required fields.223*224* @throws IllegalArgumentException if two Descriptors contain the225* same field name with different associated values. Primitive array226* values are considered the same if they are of the same type with227* the same elements. Object array values are considered the same if228* {@link Arrays#deepEquals(Object[],Object[])} returns true.229*/230public static ImmutableDescriptor union(Descriptor... descriptors) {231// Optimize the case where exactly one Descriptor is non-Empty232// and it is immutable - we can just return it.233int index = findNonEmpty(descriptors, 0);234if (index < 0)235return EMPTY_DESCRIPTOR;236if (descriptors[index] instanceof ImmutableDescriptor237&& findNonEmpty(descriptors, index + 1) < 0)238return (ImmutableDescriptor) descriptors[index];239240Map<String, Object> map =241new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER);242ImmutableDescriptor biggestImmutable = EMPTY_DESCRIPTOR;243for (Descriptor d : descriptors) {244if (d != null) {245String[] names;246if (d instanceof ImmutableDescriptor) {247ImmutableDescriptor id = (ImmutableDescriptor) d;248names = id.names;249if (id.getClass() == ImmutableDescriptor.class250&& names.length > biggestImmutable.names.length)251biggestImmutable = id;252} else253names = d.getFieldNames();254for (String n : names) {255Object v = d.getFieldValue(n);256Object old = map.put(n, v);257if (old != null) {258boolean equal;259if (old.getClass().isArray()) {260equal = Arrays.deepEquals(new Object[] {old},261new Object[] {v});262} else263equal = old.equals(v);264if (!equal) {265final String msg =266"Inconsistent values for descriptor field " +267n + ": " + old + " :: " + v;268throw new IllegalArgumentException(msg);269}270}271}272}273}274if (biggestImmutable.names.length == map.size())275return biggestImmutable;276return new ImmutableDescriptor(map);277}278279private static boolean isEmpty(Descriptor d) {280if (d == null)281return true;282else if (d instanceof ImmutableDescriptor)283return ((ImmutableDescriptor) d).names.length == 0;284else285return (d.getFieldNames().length == 0);286}287288private static int findNonEmpty(Descriptor[] ds, int start) {289for (int i = start; i < ds.length; i++) {290if (!isEmpty(ds[i]))291return i;292}293return -1;294}295296private int fieldIndex(String name) {297return Arrays.binarySearch(names, name, String.CASE_INSENSITIVE_ORDER);298}299300public final Object getFieldValue(String fieldName) {301checkIllegalFieldName(fieldName);302int i = fieldIndex(fieldName);303if (i < 0)304return null;305Object v = values[i];306if (v == null || !v.getClass().isArray())307return v;308if (v instanceof Object[])309return ((Object[]) v).clone();310// clone the primitive array, could use an 8-way if/else here311int len = Array.getLength(v);312Object a = Array.newInstance(v.getClass().getComponentType(), len);313System.arraycopy(v, 0, a, 0, len);314return a;315}316317public final String[] getFields() {318String[] result = new String[names.length];319for (int i = 0; i < result.length; i++) {320Object value = values[i];321if (value == null)322value = "";323else if (!(value instanceof String))324value = "(" + value + ")";325result[i] = names[i] + "=" + value;326}327return result;328}329330public final Object[] getFieldValues(String... fieldNames) {331if (fieldNames == null)332return values.clone();333Object[] result = new Object[fieldNames.length];334for (int i = 0; i < fieldNames.length; i++) {335String name = fieldNames[i];336if (name != null && !name.isEmpty())337result[i] = getFieldValue(name);338}339return result;340}341342public final String[] getFieldNames() {343return names.clone();344}345346/**347* Compares this descriptor to the given object. The objects are equal if348* the given object is also a Descriptor, and if the two Descriptors have349* the same field names (possibly differing in case) and the same350* associated values. The respective values for a field in the two351* Descriptors are equal if the following conditions hold:352*353* <ul>354* <li>If one value is null then the other must be too.</li>355* <li>If one value is a primitive array then the other must be a primitive356* array of the same type with the same elements.</li>357* <li>If one value is an object array then the other must be too and358* {@link Arrays#deepEquals(Object[],Object[])} must return true.</li>359* <li>Otherwise {@link Object#equals(Object)} must return true.</li>360* </ul>361*362* @param o the object to compare with.363*364* @return {@code true} if the objects are the same; {@code false}365* otherwise.366*367*/368// Note: this Javadoc is copied from javax.management.Descriptor369// due to 6369229.370@Override371public boolean equals(Object o) {372if (o == this)373return true;374if (!(o instanceof Descriptor))375return false;376String[] onames;377if (o instanceof ImmutableDescriptor) {378onames = ((ImmutableDescriptor) o).names;379} else {380onames = ((Descriptor) o).getFieldNames();381Arrays.sort(onames, String.CASE_INSENSITIVE_ORDER);382}383if (names.length != onames.length)384return false;385for (int i = 0; i < names.length; i++) {386if (!names[i].equalsIgnoreCase(onames[i]))387return false;388}389Object[] ovalues;390if (o instanceof ImmutableDescriptor)391ovalues = ((ImmutableDescriptor) o).values;392else393ovalues = ((Descriptor) o).getFieldValues(onames);394return Arrays.deepEquals(values, ovalues);395}396397/**398* <p>Returns the hash code value for this descriptor. The hash399* code is computed as the sum of the hash codes for each field in400* the descriptor. The hash code of a field with name {@code n}401* and value {@code v} is {@code n.toLowerCase().hashCode() ^ h}.402* Here {@code h} is the hash code of {@code v}, computed as403* follows:</p>404*405* <ul>406* <li>If {@code v} is null then {@code h} is 0.</li>407* <li>If {@code v} is a primitive array then {@code h} is computed using408* the appropriate overloading of {@code java.util.Arrays.hashCode}.</li>409* <li>If {@code v} is an object array then {@code h} is computed using410* {@link Arrays#deepHashCode(Object[])}.</li>411* <li>Otherwise {@code h} is {@code v.hashCode()}.</li>412* </ul>413*414* @return A hash code value for this object.415*416*/417// Note: this Javadoc is copied from javax.management.Descriptor418// due to 6369229.419@Override420public int hashCode() {421if (hashCode == -1) {422hashCode = Util.hashCode(names, values);423}424return hashCode;425}426427@Override428public String toString() {429StringBuilder sb = new StringBuilder("{");430for (int i = 0; i < names.length; i++) {431if (i > 0)432sb.append(", ");433sb.append(names[i]).append("=");434Object v = values[i];435if (v != null && v.getClass().isArray()) {436String s = Arrays.deepToString(new Object[] {v});437s = s.substring(1, s.length() - 1); // remove [...]438v = s;439}440sb.append(String.valueOf(v));441}442return sb.append("}").toString();443}444445/**446* Returns true if all of the fields have legal values given their447* names. This method always returns true, but a subclass can448* override it to return false when appropriate.449*450* @return true if the values are legal.451*452* @exception RuntimeOperationsException if the validity checking fails.453* The method returns false if the descriptor is not valid, but throws454* this exception if the attempt to determine validity fails.455*/456public boolean isValid() {457return true;458}459460/**461* <p>Returns a descriptor which is equal to this descriptor.462* Changes to the returned descriptor will have no effect on this463* descriptor, and vice versa.</p>464*465* <p>This method returns the object on which it is called.466* A subclass can override it467* to return another object provided the contract is respected.468*469* @exception RuntimeOperationsException for illegal value for field Names470* or field Values.471* If the descriptor construction fails for any reason, this exception will472* be thrown.473*/474@Override475public Descriptor clone() {476return this;477}478479/**480* This operation is unsupported since this class is immutable. If481* this call would change a mutable descriptor with the same contents,482* then a {@link RuntimeOperationsException} wrapping an483* {@link UnsupportedOperationException} is thrown. Otherwise,484* the behavior is the same as it would be for a mutable descriptor:485* either an exception is thrown because of illegal parameters, or486* there is no effect.487*/488public final void setFields(String[] fieldNames, Object[] fieldValues)489throws RuntimeOperationsException {490if (fieldNames == null || fieldValues == null)491illegal("Null argument");492if (fieldNames.length != fieldValues.length)493illegal("Different array sizes");494for (int i = 0; i < fieldNames.length; i++)495checkIllegalFieldName(fieldNames[i]);496for (int i = 0; i < fieldNames.length; i++)497setField(fieldNames[i], fieldValues[i]);498}499500/**501* This operation is unsupported since this class is immutable. If502* this call would change a mutable descriptor with the same contents,503* then a {@link RuntimeOperationsException} wrapping an504* {@link UnsupportedOperationException} is thrown. Otherwise,505* the behavior is the same as it would be for a mutable descriptor:506* either an exception is thrown because of illegal parameters, or507* there is no effect.508*/509public final void setField(String fieldName, Object fieldValue)510throws RuntimeOperationsException {511checkIllegalFieldName(fieldName);512int i = fieldIndex(fieldName);513if (i < 0)514unsupported();515Object value = values[i];516if ((value == null) ?517(fieldValue != null) :518!value.equals(fieldValue))519unsupported();520}521522/**523* Removes a field from the descriptor.524*525* @param fieldName String name of the field to be removed.526* If the field name is illegal or the field is not found,527* no exception is thrown.528*529* @exception RuntimeOperationsException if a field of the given name530* exists and the descriptor is immutable. The wrapped exception will531* be an {@link UnsupportedOperationException}.532*/533public final void removeField(String fieldName) {534if (fieldName != null && fieldIndex(fieldName) >= 0)535unsupported();536}537538static Descriptor nonNullDescriptor(Descriptor d) {539if (d == null)540return EMPTY_DESCRIPTOR;541else542return d;543}544545private static void checkIllegalFieldName(String name) {546if (name == null || name.isEmpty())547illegal("Null or empty field name");548}549550private static void unsupported() {551UnsupportedOperationException uoe =552new UnsupportedOperationException("Descriptor is read-only");553throw new RuntimeOperationsException(uoe);554}555556private static void illegal(String message) {557IllegalArgumentException iae = new IllegalArgumentException(message);558throw new RuntimeOperationsException(iae);559}560}561562563