/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2005-2017 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://oss.oracle.com/licenses/CDDL+GPL-1.1
* or LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package devtests.deployment.util;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Handler;
import java.util.logging.Logger;
import java.util.logging.Level;
import org.glassfish.deployment.common.Descriptor;
import com.sun.enterprise.util.LocalStringManagerImpl;
/**
* This class compares deployment Descriptors for testing purpose.
* It is not thread safe.
*
* @author Shing Wai Chan
*/
public class DescriptorContentComparator {
private static final Class[] excludedClasses = new Class[] {
java.lang.Object.class,
java.beans.PropertyChangeSupport.class,
com.sun.enterprise.util.LocalStringManagerImpl.class,
java.util.Observable.class
};
private static final String[] excludedFieldNames = new String[] {
"propListeners",
"wsdlPortNamespacePrefix",
"specVersion"
};
private static Logger logger = null;
// for easy debug
private Field lastField = null;
public DescriptorContentComparator() {
initLogger();
}
public boolean compareContent(Descriptor d1, Descriptor d2) {
boolean result = compareContent(null, d1, d2, new HashSet());
if (!result) {
logger.severe("last compared Field = " + lastField);
}
return result;
}
/**
* Use reflection to look at fields for comparison.
* It is important to note that if instances under comparison are in
* parameter <i>set</i>, then the comparison result will always be
* true. This resolves the issues of circular references.
* @param field Field under comparison if known
* @param o1 Object to be compared
* @param o2 Object to be compared
* @param set stack of Descriptor instances under comparison
* @return boolean result of comparison
*/
private boolean compareContent(Field field, Object o1, Object o2, Set set) {
if (o1 == o2 ||
isNullEquivalent(field, o1) && isNullEquivalent(field, o2)) {
return true;
}
if (o1 == null && o2 != null || o1 != null && o2 == null ||
!o2.getClass().equals(o1.getClass()) ||
isExcludedClass(o1.getClass())) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("... null, class mismatch or excluded for " +
((field != null)? field : "") +
", o1 = " + o1 + ", o2 = " + o2);
}
return false;
}
// in the following, o1 and o2 are not null
Package p1 = o1.getClass().getPackage();
Package p2 = o2.getClass().getPackage();
if (p1 != null && !p1.equals(p2) || p1 == null && p2 != null) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("... diff package: obj1 = " + o1);
logger.fine("... diff package: obj2 = " + o2);
}
return false;
} else if (o1.getClass().isArray()) {
if (!compareArrayContent(o1, o2, set)) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("... diff array: obj1 = " + o1);
logger.fine("... diff array: obj2 = " + o2);
}
return false;
}
} else if (o1 instanceof Collection) {
if (!compareCollectionContent((Collection)o1,
(Collection)o2, set)) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("... diff coll: obj1 = " + o1);
logger.fine("... diff coll: obj2 = " + o2);
}
return false;
}
} else if (o1 instanceof Map) {
if (!compareMapContent((Map)o1, (Map)o2, set)) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("... diff map: obj1 = " + o1);
logger.fine("... diff map: obj2 = " + o2);
}
return false;
}
} else if (p1 != null &&
(p1.getName().startsWith("com.sun.enterprise.deployment") || p1.getName().startsWith("org.glassfish.ejb.deployment") || p1.getName().startsWith("org.glassfish.web.deployment"))
) {
if (o1 instanceof Descriptor) {
// to handle circular reference
ReferencePair rd = new ReferencePair(o1, o2);
if (!set.add(rd)) {
return true; // continue processing
}
}
// looping from subclass to superclass
Class clazz = o2.getClass();
while (clazz != null && !isExcludedClass(clazz)) {
if (logger.isLoggable(Level.FINER)) {
logger.finer("clazz = " + clazz);
}
Field[] declaredFields = clazz.getDeclaredFields();
for (Field df : declaredFields) {
try {
lastField = df;
if (logger.isLoggable(Level.FINER)) {
logger.finer("... \tdf = " + df);
}
if (isExcludedNamedField(df) ||
Modifier.isStatic(df.getModifiers())) {
continue;
}
df.setAccessible(true); // to see private fields
Object v1 = df.get(o1);
Object v2 = df.get(o2);
if (!compareContent(df, v1, v2, set)) {
return false;
}
} catch(Exception ex) {
throw new IllegalStateException(ex);
}
}
clazz = clazz.getSuperclass();
}
} else {
boolean result = o1.equals(o2);
if (!result && logger.isLoggable(Level.FINE)) {
logger.fine("... diff content: obj1 = " + o1);
logger.fine("... diff content: obj2 = " + o2);
}
return result;
}
return true;
}
/**
* The following are regarded as null for comparison purposes:
* Collection, Map of size 0
* String of size 0
* @param df field under comparison if known
* @param o object under comparison
*/
private boolean isNullEquivalent(Field df, Object o) {
boolean result = false;
if (o == null) {
result = true;
} else if (o instanceof Collection) {
Collection coll = (Collection)o;
result = (coll.size() == 0);
} else if (o instanceof Map) {
Map map = (Map)o;
int size = map.size();
if (size == 0) {
result = true;
// Need to ignore dynamicAttributes with prefix-mapping entry.
} else if (size == 1) {
if (df != null && "dynamicAttributes".equals(df.getName())) {
result = (map.get("prefix-mapping") != null);
}
}
} else if (o instanceof String) {
String s = (String)o;
result = (s.length() == 0);
}
return result;
}
private boolean isExcludedClass(Class cl) {
boolean result = false;
for (Class clazz : excludedClasses) {
if (clazz.equals(cl)) {
result = true;
break;
}
}
return result;
}
private boolean isExcludedNamedField(Field f) {
boolean result = false;
for (String ef : excludedFieldNames) {
if (ef.equals(f.getName())) {
result = true;
break;
}
}
return result;
}
/**
* Assume arguments not null.
*/
private boolean compareArrayContent(Object value1, Object value2, Set set) {
int len1 = Array.getLength(value1);
int len2 = Array.getLength(value2);
if (len1 != len2) {
return false;
} else {
for (int i = 0; i < len1; i++) {
Object indObj1 = Array.get(value1, i);
Object indObj2 = Array.get(value2, i);
if (!(indObj1 == null && indObj2 == null) &&
!(indObj1 != null && indObj2 != null &&
compareContent(null, indObj1, indObj2, set))) {
return false;
}
}
}
return true;
}
/**
* Assume arguments not null.
*/
private boolean compareCollectionContent(Collection coll1,
Collection coll2, Set set) {
int size1 = coll1.size();
int size2 = coll2.size();
if (logger.isLoggable(Level.FINEST)) {
logger.finest("compare collection with sizes " + size1 + " and " + size2);
}
if (size1 != size2) {
return false;
}
Iterator iter1 = coll1.iterator();
// O(n^2) comparison!!!
while (iter1.hasNext()) {
Object obj1 = iter1.next();
Iterator iter2 = coll2.iterator();
boolean tempResult = false;
while (iter2.hasNext()) {
Object obj2 = iter2.next();
if (compareContent(null, obj1, obj2, set)) {
tempResult = true;
break;
}
}
if (!tempResult) {
return false;
}
}
return true;
}
/**
* Assume arguments not null.
*/
private boolean compareMapContent(Map map1, Map map2, Set set) {
Set keySet1 = map1.keySet();
Set keySet2 = map2.keySet();
if (!compareCollectionContent(keySet1, keySet2, set)) {
return false;
}
Iterator iter = keySet1.iterator();
while (iter.hasNext()) {
Object key = iter.next();
if (!compareContent(null, map1.get(key), map2.get(key), set)) {
return false;
}
}
return true;
}
private void initLogger() {
logger = Logger.getLogger(DescriptorContentComparator.class.getName());
Level logLevel = Boolean.getBoolean("debug") ?
Level.FINEST : Level.CONFIG;
for (Handler h : Logger.getLogger("").getHandlers()) {
h.setLevel(logLevel);
}
logger.setLevel(logLevel);
}
/**
* This class represents a pair of references where order is not important.
*/
class ReferencePair {
Object c1;
Object c2;
int hashCode = 0;
ReferencePair(Object c1, Object c2) {
this.c1 = c1;
this.c2 = c2;
if (c1 != null) {
hashCode = c1.hashCode();
}
if (c2 != null) {
hashCode ^= c2.hashCode();
}
}
public int hashCode() {
return hashCode;
}
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof ReferencePair)) {
return false;
}
ReferencePair rd = (ReferencePair)o;
return (c1 == rd.c1 && c2 == rd.c2 ||
c1 == rd.c2 && c2 == rd.c1);
}
}
}