/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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 net.jini.entry; import net.jini.core.entry.Entry; import java.lang.reflect.*; import java.util.WeakHashMap; import java.util.ArrayList; /** * An abstract implementation of {@link Entry} that provides useful * implementations of <code>equals</code>, <code>hashCode</code>, and * <code>toString</code>. Implementations of the <code>Entry</code> * interface may, but are not required to, extend this class. <p> * * The methods of this class consult the <em>entry fields</em> of the * entries they process. The entry fields of an <code>Entry</code> are * its public, non-primitive, non-static, non-transient, non-final * fields.<p> * * @author Sun Microsystems, Inc. * @see Entry */ public abstract class AbstractEntry implements Entry { static final long serialVersionUID = 5071868345060424804L; /** * Creates an instance of this class. */ protected AbstractEntry() { } /** * Compares this <code>AbstractEntry</code> to the specified * object. If <code>other</code> is <code>null</code> or not an * instance of {@link Entry} returns <code>false</code>, * otherwise returns the result of calling {@link #equals(Entry,Entry) * AbstractEntry.equals(this, (Entry) other)}. * @param other the object to compare this <code>AbstractEntry</code> * against * @return <code>true</code> if this object is equivalent * to <code>other</code> and <code>false</code> otherwise */ public boolean equals(Object other) { if (!(other instanceof Entry)) return false; return equals(this, (Entry) other); } /** * Returns <code>true</code> if the two arguments are of the same * class and for each entry field <em>F</em>, the arguments' * values for <em>F</em> are either both <code>null</code> or the * invocation of <code>equals</code> on one argument's value for * <em>F</em> with the other argument's value for <em>F</em> as * its parameter returns <code>true</code>. Will also return * <code>true</code> if both arguments are <code>null</code>. In * all other cases an invocation of this method will return * <code>false</code>.<p> * * @param e1 an entry object to compare to e2 * @param e2 an entry object to compare to e1 * @return <code>true</code> if the two arguments are equivalent */ public static boolean equals(Entry e1, Entry e2) { if (e1 == e2) return true; // Note, if both e1 and e2 are null the previous test would // have returned true. if (e1 == null || e2 == null) return false; if (e1.getClass() != e2.getClass()) return false; Field[] fields = fieldInfo(e1); try { // compare each field for (int i = 0; i < fields.length; i++) { // f works for other since other is the same type as this Field f = fields[i]; Object ov = f.get(e1); Object tv = f.get(e2); if (tv == ov) // same obj or both null is OK continue; if (tv == null || ov == null) // if only one is null, not OK return false; if (!tv.equals(ov)) // not equals is not OK return false; } return true; } catch (IllegalAccessException e) { // should never happen, all entry fields are public throw new AssertionError(e); } } /** * Returns the result of calling {@link #hashCode(Entry) * AbstractEntry.hashCode(this)}. * @return the result of calling {@link #hashCode(Entry) * AbstractEntry.hashCode(this)} */ public int hashCode() { return hashCode(this); } /** * Returns zero XORed with the result of invoking * <code>hashCode</code> on each of the argument's * non-<code>null</code> entry fields. Returns <code>0</code> if * the argument is <code>null</code>. * * @param entry the <code>Entry</code> for which to generate a * hash code * @return a hash code formed by XORing the hash codes of * <code>entry</code>'s non-<code>null</code> entry field, * or <code>0</code> if <code>entry</code> is * <code>null</code> */ public static int hashCode(Entry entry) { if (entry == null) return 0; int hash = 0; Field[] fields = fieldInfo(entry); try { for (int i = 0; i < fields.length; i++) { Object tv = fields[i].get(entry); if (tv != null) hash ^= tv.hashCode(); } return hash; } catch (IllegalAccessException e) { // should never happen, all entry fields are public throw new AssertionError(e); } } /** * Returns the result of calling {@link #toString(Entry) * AbstractEntry.toString(this)}. * @return the result of calling {@link #toString(Entry) * AbstractEntry.toString(this)} */ public String toString() { return toString(this); } /** * Returns a <code>String</code> representation of its argument * that will contain the name of the argument's class and a * representation of each of the argument's entry fields. The * representation of each entry field will include the field's * name and a representation of its value. If passed * <code>null</code> will return the string <code>"null"</code>. <p> * * @param entry an entry to represent as a String * @return a <code>String</code> representation of * <code>entry</code> that contains the name of the * <code>entry</code>'s class and a representation of each * of <code>entry</code>'s entry fields */ public static String toString(Entry entry) { if (entry == null) return "null"; Field[] fields = fieldInfo(entry); StringBuffer str = new StringBuffer(entry.getClass().getName()); str.append('('); // later matched with a ')' for (int i = 0; i < fields.length; i++) { try { Field f = fields[i]; if (i > 0) str.append(','); str.append(f.getName()); str.append('='); str.append(f.get(entry)); } catch (IllegalAccessException e) { // should never happen, all entry fields are public throw new AssertionError(e); } } // now add the ending '(' str.append(')'); return str.toString(); } private static WeakHashMap fieldArrays; /** * Calculate the list of usable fields for this type */ private static Field[] fieldInfo(Entry entry) { Field[] fields = null; synchronized (AbstractEntry.class) { if (fieldArrays == null) fieldArrays = new WeakHashMap(); else { fields = (Field[]) fieldArrays.get(entry.getClass()); if (fields != null) return fields; } } /* * Scan the array to see if we can use it or if we must build up * a smaller array because we must skip some fields. If so, we * create an ArrayList and add the unskippable fields to it, and * then fetch the array back out of it. */ final int SKIP_MODIFIERS = (Modifier.STATIC | Modifier.TRANSIENT | Modifier.FINAL); fields = entry.getClass().getFields(); ArrayList usable = null; for (int i = 0; i < fields.length; i++) { // exclude this one? if ((fields[i].getModifiers() & SKIP_MODIFIERS) != 0 || (fields[i].getType().isPrimitive())) { if (usable == null) { // first excluded: set up for it usable = new ArrayList(); // allocate the list of usable for (int j = 0; j < i; j++) // earlier fields are usable usable.add(fields[j]); } } else { // not excluded if (usable != null) // tracking usable fields? usable.add(fields[i]); } } if (usable != null) fields = (Field[]) usable.toArray(new Field[usable.size()]); synchronized (AbstractEntry.class) { // We could check to make sure someone else // has not already stuck a value for entry.getClass() in, // fieldArrays but there should be no harm in an overwrite // and if anything likely to be less efficient fieldArrays.put(entry.getClass(), fields); } return fields; } }