/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2011
* (in the framework of the ALMA collaboration).
* All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*******************************************************************************/
package alma.acs.container.corba;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import org.omg.CORBA.portable.IDLEntity;
/**
* This class finds illegal null values in corba parameters or return values, such as null strings inside structs.
* It uses java reflection to navigate the possibly nested data.
* <p>
* Motivation: On the server side of a corba call, the implementation cannot catch and evaluate the resulting
* marshalling or NullPointer exceptions, which are often very unclear about the cause of the problem.
* Thus to help with debugging, this class can be used to analyze return values or out parameters,
* and report possible problems in an understandable way.
* Alternatively, this class can also be used on the client side of a corba call, although it will be less useful there,
* because marshalling errors for client data produces rather tangible exceptions.
* <p>
* One CorbaNullFinder object must be constructed for every corba parameter / return value to be checked.
* Then calling the methods {@link #hasErrors()} or {@link #getErrors()} will run the check
* and report the result.
*
* @author hsommer
* @since ACS 9.0 (see )
*/
public class CorbaNullFinder
{
private final Object corbaData;
private List<String> errors;
/**
* Constructor from the top-level corba parameter or return value.
* Must not be used for corba object references (e.g. offshoot references), which may be null,
* but cannot be distinguished from illegal null structs etc.
*/
public CorbaNullFinder(Object corbaData) {
this.corbaData = corbaData;
}
/**
* The actual worker method, called by {@link #hasErrors()} or {@link #getErrors()}.
*/
synchronized private void checkForNulls() {
if (errors == null) {
errors = new ArrayList<String>();
if (corbaData != null) {
String path = corbaData.getClass().getSimpleName();
recursiveCheckForNulls(corbaData, path);
}
else {
errors.add("Top-level object is null; cannot distinguish between a legal null object reference and an illegal null data item.");
}
}
}
/**
* @param _corbaData
* @param path
*/
private void recursiveCheckForNulls(Object _corbaData, String path) {
if (_corbaData == null) {
// happens when array members are null
errors.add("Null object in field " + path);
return;
}
if (_corbaData.getClass().getPackage() != null && _corbaData.getClass().getPackage().getName().startsWith("java")) {
// don't recurse into the fields of String etc
return;
}
else if (_corbaData.getClass().isArray()) {
// recurse into the members of this array
for (int i = 0; i < Array.getLength(_corbaData); i++) {
String recPath = (path.endsWith("[]") ? path.substring(0, path.length()-1) + i + "]" : path);
recursiveCheckForNulls(Array.get(_corbaData, i), recPath);
}
}
else {
// recurse into the fields of this class
Field[] fields = _corbaData.getClass().getFields();
for (Field field : fields) {
Class<?> clzz = field.getType();
String qualifiedFieldName = path + "/" + field.getName();
try {
Object value = field.get(_corbaData);
if (clzz.isPrimitive()) {
// nothing to do
}
else if (clzz == String.class) {
if (value == null) {
errors.add("Null string in field " + qualifiedFieldName);
}
}
else if (isIDLEnumClass(clzz)) {
if (value == null) {
errors.add("Null enum in field " + qualifiedFieldName);
}
}
else if (isIDLStructClass(clzz)) {
if (value == null) {
errors.add("Null struct in field " + qualifiedFieldName);
}
else {
recursiveCheckForNulls(value, qualifiedFieldName);
}
}
else if (clzz.isArray()) {
if (value == null) {
errors.add("Null array in field " + qualifiedFieldName);
}
else {
recursiveCheckForNulls(value, qualifiedFieldName + "[]");
}
}
else if (!isIDLInterfaceClass(clzz)) {
// @TODO: check test output, and eventually remove this println!
System.out.println("DEBUG: Check if we need to update " + CorbaNullFinder.class.getName() + " to support " + clzz.getName()
+ " used in " + qualifiedFieldName);
}
} catch (Exception ex) {
errors.add("Failed to read field of type " + clzz.getName());
}
}
}
}
static boolean isIDLEnumClass(Class<?> clzz) {
if (IDLEntity.class.isAssignableFrom(clzz) &&
!org.omg.CORBA.Object.class.isAssignableFrom(clzz)) {
try {
clzz.getMethod("from_int", int.class); // specified in IDL-to-Java mapping spec chapter 1.7
return true; // an IDL enum!
} catch (Exception ex) {
return false; // an IDL struct
}
}
else {
return false; // maybe a String, or an IDL interface
}
}
static boolean isIDLStructClass(Class<?> clzz) {
if (IDLEntity.class.isAssignableFrom(clzz) &&
!org.omg.CORBA.Object.class.isAssignableFrom(clzz)) {
try {
clzz.getMethod("from_int", int.class);
return false; // an IDL enum
} catch (Exception ex) {
return true; // an IDL struct
}
}
else {
return false; // maybe a String, or an IDL interface
}
}
public static boolean isIDLInterfaceClass(Class<?> clzz) {
return ( IDLEntity.class.isAssignableFrom(clzz) && org.omg.CORBA.Object.class.isAssignableFrom(clzz) );
}
/**
* @return true if there were errors in the corba data passed to the constructor, false otherwise.
*/
public boolean hasErrors() {
checkForNulls();
return errors.size() > 0;
}
/**
* Gets the textual description of the errors found, including the pathname of the affected data item(s).
* @return List of errors found, or empty list if there are no errors.
*/
public List<String> getErrors() {
checkForNulls();
return new ArrayList<String>(errors);
}
}