/**
* JHOVE2 - Next-generation architecture for format-aware characterization
*
* Copyright (c) 2009 by The Regents of the University of California,
* Ithaka Harbors, Inc., and The Board of Trustees of the Leland Stanford
* Junior University.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of the University of California/California Digital
* Library, Ithaka Harbors/Portico, or Stanford University, nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jhove2.app.util;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.jhove2.annotation.ReportableProperty;
import org.jhove2.core.I8R;
import org.jhove2.core.JHOVE2Exception;
import org.jhove2.core.reportable.Reportable;
import org.jhove2.core.reportable.info.ReportableInfo;
import org.jhove2.core.reportable.info.ReportablePropertyComparator;
import org.jhove2.core.reportable.info.ReportablePropertyInfo;
import org.jhove2.core.reportable.info.ReportableSourceInfo;
/**
* Base class for utility applications to generate .properties files for
* JHOVE2 {@link org.jhove2.core.reportable.Reportable} classes.
* @author smorrissey
*
*/
public class FeatureConfigurationUtil {
/** list of Strings representing all boolean class type names */
public static ArrayList<String> booleanTypes;
/** list of Strings representing all numeric class type names */
public static ArrayList<String> numericTypes;
public static final String BOOLEAN_TYPE = "boolean";
public static final String BOOLEAN = "java.lang.Boolean";
public static final String SHORT_TYPE = "short";
public static final String INTEGER_TYPE = "int";
public static final String LONG_TYPE = "long";
public static final String FLOAT_TYPE = "float";
public static final String DOUBLE_TYPE = "double";
public static final String SHORT = "java.lang.Short";
public static final String INTEGER = "java.lang.Integer";
public static final String LONG = "java.lang.Long";
public static final String FLOAT = "java.lang.Float";
public static final String DOUBLE = "java.lang.Double";
public static final String BIG_DECIMAL = "java.math.BigDecimal";
public static final String BIG_INTEGER = "java.math.BigInteger";
public static final String NUMBER = "java.lang.Number";
public static final String ATOMIC_INTEGER = "java.util.concurrent.atomic.AtomicInteger";
public static final String ATOMIC_LONG = "java.util.concurrent.atomic.AtomicLong";
/** separator character for choices in editable properties files */
public static final String OR = " | ";
/**
* Creates {@link org.jhove2.core.reportable.Reportable} object corresponding
* to class name passed as parameter
* @param className name of class for which to create a
* {@link org.jhove2.core.reportable.Reportable} instance
* @return {@link org.jhove2.core.reportable.Reportable}
* @throws JHOVE2Exception if class name represents a class which does not extend
* {@link org.jhove2.core.reportable.Reportable}, or for any other
* exception thrown, or if Reportable does not have 0-argument constructor
*/
public static Reportable getReportableFromClassName(
String className) throws JHOVE2Exception {
Class <? extends Reportable> rClass = null;
Reportable reportable = null;
// try {
try {
rClass = (Class<? extends Reportable>)(Class.forName(className));
reportable = (rClass.newInstance());
} catch (ClassNotFoundException e1) {
throw new JHOVE2Exception(
"ClassNotFoundException trying to create Reportable object from classname "
+ className, e1);
} catch (InstantiationException e) {
throw new JHOVE2Exception(
"Instantiation Exception tyring to create Reportable object from classname "
+ className, e);
} catch (IllegalAccessException e) {
throw new JHOVE2Exception(
"IllegalAccess Exception tyring to create Reportable object from classname "
+ className, e);
}
return reportable;
}
/**
* Return list of all property {@link org.jhove2.core.I8R} value strings for a
* {@link org.jhove2.core.reportable.Reportable} object
* @param reportable {@link org.jhove2.core.reportable.Reportable} object
* @return List containing all property {@link org.jhove2.core.I8R} value strings
*/
public static List<String> getPropertiesAsList(Reportable reportable){
ArrayList<String> propsList = new ArrayList<String>();
ReportableInfo reportableInfo = new ReportableInfo(reportable);
for (ReportableSourceInfo rsInfo:reportableInfo.getProperties()){
for (ReportablePropertyInfo rpInfo : rsInfo.getProperties()){
String idString = rpInfo.getIdentifier().getValue();
propsList.add(idString);
}
}
return propsList;
}
/**
* Return list of all {@link org.jhove2.core.reportable.info.ReportablePropertyInfo} for all reportable properties
* of a {@link org.jhove2.core.reportable.Reportable} object
* @param reportable {@link org.jhove2.core.reportable.Reportable} object
* @return List containing all property {@link org.jhove2.core.reportable.info.ReportablePropertyInfo} for Reportable
*/
public static List<ReportablePropertyInfo> getPropertiesAsReportablePropertyInfoList
(Reportable reportable){
ArrayList<ReportablePropertyInfo> propsList = new ArrayList<ReportablePropertyInfo>();
ReportableInfo reportableInfo = new ReportableInfo(reportable);
for (ReportableSourceInfo rsInfo:reportableInfo.getProperties()){
for (ReportablePropertyInfo rpInfo : rsInfo.getProperties()){
propsList.add(rpInfo);
}
}
return propsList;
}
/**
* Get set of all {@link org.jhove2.core.reportable.info.ReportablePropertyInfo} for a class if it is a
* {@link org.jhove2.core.reportable.Reportable} class. This legacy method assumes you want to retrieve
* ReportableProperties inherited from interfaces and super-classes
* @param className Name of class for which we want properties
* @return set of all {@link org.jhove2.core.reportable.info.ReportablePropertyInfo} for that class
* @throws JHOVE2Exception if class does not implement {@link org.jhove2.core.reportable.Reportable}
*/
public static Set<ReportablePropertyInfo> getProperitiesAsReportablePropertyInfoSet (String className)
throws JHOVE2Exception {
return getProperitiesAsReportablePropertyInfoSet(className, true);
}
/**
* Get set of all {@link org.jhove2.core.reportable.info.ReportablePropertyInfo} for a class if it is a
* {@link org.jhove2.core.reportable.Reportable} class
* @param className Name of class for which we want properties
* @param includeAncestors Specifies whether to include properties inherited from interfaces and super-classes
* @return set of all {@link org.jhove2.core.reportable.info.ReportablePropertyInfo} for that class
* @throws JHOVE2Exception if class does not implement {@link org.jhove2.core.reportable.Reportable}
*/
public static Set<ReportablePropertyInfo> getProperitiesAsReportablePropertyInfoSet (String className, boolean includeAncestors)
throws JHOVE2Exception {
Class<? extends Reportable> cl = null;
ReportablePropertyComparator comparator = new ReportablePropertyComparator();
Map<String, String> idMap = new HashMap<String, String>();
try {
cl = (Class<? extends Reportable>) Class.forName(className);
} catch (ClassNotFoundException e) {
cl= getReportableFromInnerClassName(className);
if (cl==null){
throw new JHOVE2Exception("Cannot create Reportable class for className " + className, e);
}
}
Set<ReportablePropertyInfo> set = new TreeSet<ReportablePropertyInfo>(
comparator);
do{
Method[] methods = cl.getDeclaredMethods();
for (int j = 0; j < methods.length; j++) {
ReportableProperty annot = methods[j]
.getAnnotation(ReportableProperty.class);
if (annot != null) {
// construct an I8R for each reportable field using
// the field's accessor method
I8R featureId = I8R.makeFeatureI8RFromMethod(methods[j], cl);
if (idMap.get(featureId.getValue()) == null) {
idMap.put(featureId.getValue(), featureId.getValue());
ReportablePropertyInfo prop = new ReportablePropertyInfo(
featureId, methods[j], annot.value(),
annot.ref(), annot.type());
set.add(prop);
}// end if we don't already have this feature
}// end if (annot != null)
}// end for
if (includeAncestors) {
checkInterfaces(cl.getInterfaces(), idMap, comparator, set);
}
} while (includeAncestors && ((cl = (Class<? extends Reportable>) cl.getSuperclass()) != null));
return set;
}
/**
* Introspect on interfaces (and superinterfaces) to retrieve reportable
* properties.
*
* @param ifs
* Interfaces to examine
* @param idMap
* Map of properties identifiers already retrieved
* @param comparator
* Reportable property comparator
*/
public static void checkInterfaces(Class<?>[] ifs, Map<String, String> idMap,
ReportablePropertyComparator comparator, Set<ReportablePropertyInfo> rpiSet) {
for (int i = 0; i < ifs.length; i++) {
Set<ReportablePropertyInfo> set = new TreeSet<ReportablePropertyInfo>(
comparator);
Method[] methods = ifs[i].getDeclaredMethods();
for (int j = 0; j < methods.length; j++) {
ReportableProperty annot = methods[j]
.getAnnotation(ReportableProperty.class);
if (annot != null) {
// construct an I8R for each reportable field using
// the field's accessor method
Class<? extends Reportable> repClass = (Class<? extends Reportable>)ifs[i];
I8R featureId = I8R.makeFeatureI8RFromMethod(methods[j], repClass);
if (idMap.get(featureId.getValue()) == null) {
idMap.put(featureId.getValue(), featureId.getValue());
ReportablePropertyInfo prop = new ReportablePropertyInfo(
featureId, methods[j], annot.value(),
annot.ref(), annot.type());
set.add(prop);
}
}
}
if (set.size() > 0) {
rpiSet.addAll(set);
}
checkInterfaces(ifs[i].getInterfaces(), idMap, comparator, rpiSet);
}
}
/**
* Test if Type is boolean
* @param type to be tested
* @return true if boolean, else false
* @throws JHOVE2Exception
*/
public static boolean isBooleanType(Type type){
boolean isBoolean = false;
if (!isParameterizedType(type)){
Class<?> tClass = (Class<?>)type;
String className = tClass.getName();
if (getBooleanTypes().contains(className)){
isBoolean = true;
}
}
return isBoolean;
}
/**
* Test if type is numeric
* @param type to be tested
* @return true if numeric, else false
* @throws JHOVE2Exception
*/
public static boolean isNumericType(Type type){
boolean isNumeric = false;
if (!isParameterizedType(type)){
Class<?> tClass = (Class<?>)type;
String className = tClass.getName();
if (getNumericTypes().contains(className)){
isNumeric = true;
}
}
return isNumeric;
}
/**
* Tests if Type is a Parameterized Type
* @param Type to be tested
* @return true if Type is Parameterized type; else false
*/
public static boolean isParameterizedType(Type type){
boolean isPType = false;
try {
ParameterizedType pType = (ParameterizedType)type;
isPType = true;
}
catch(Exception e){;}
return isPType;
}
/**
* Get list of Strings representing all boolean class type names
* @return the booleanTypes
*/
public static ArrayList<String> getBooleanTypes() {
if (booleanTypes == null){
ArrayList<String> bTypes = new ArrayList<String>();
bTypes.add(BOOLEAN_TYPE);
bTypes.add(BOOLEAN);
booleanTypes = bTypes;
}
return booleanTypes;
}
/**
* Get list of Strings representing all numeric class type names
* @return the numericTypes
*/
public static ArrayList<String> getNumericTypes() {
if (numericTypes == null){
ArrayList<String> ntypes = new ArrayList<String>();
ntypes.add(SHORT_TYPE);
ntypes.add(INTEGER_TYPE);
ntypes.add(LONG_TYPE);
ntypes.add(FLOAT_TYPE);
ntypes.add(DOUBLE_TYPE);
ntypes.add(SHORT);
ntypes.add(INTEGER);
ntypes.add(LONG);
ntypes.add(FLOAT);
ntypes.add(DOUBLE);
ntypes.add(BIG_DECIMAL);
ntypes.add(BIG_INTEGER);
ntypes.add(NUMBER);
ntypes.add(ATOMIC_INTEGER);
ntypes.add(ATOMIC_LONG);
numericTypes = ntypes;
}
return numericTypes;
}
/**
* Test if class implements {@link org.jhove2.core.reportable.Reportable}
* @param className
* @return true if class implements {@link org.jhove2.core.reportable.Reportable},
* else false
*/
public static boolean isReportableClass(String className){
boolean isReportable = false;
try {
Class<?> tClass = Class.forName(className);
Class<?> rClass = Class.forName("org.jhove2.core.reportable.Reportable");
if (rClass.isAssignableFrom(tClass)){
isReportable = true;
}
} catch (ClassNotFoundException e) {}
return isReportable;
}
/**
* Determine if class is an inner, but still reportable, class
* @param className name of class to be tested
* @return if class implements {@link org.jhove2.core.reportable.Reportable},
* else false
*/
public static boolean isReportableInnerClass(String className){
boolean isReportable = false;
// check to see if this in inner class (we only go up one level)
Class<? extends Reportable> reportable = getReportableFromInnerClassName(className);
if (reportable != null){
isReportable = true;
}
return isReportable;
}
/**
* Create Class object for className if className is name of public Reportable inner class
* @param className
* @return Class object for className, or null if className is not name of public Reportable inner class
*/
public static Class<? extends Reportable> getReportableFromInnerClassName(String className){
Class<? extends Reportable> reportable = null;
int i = className.lastIndexOf(".");
if (i>0 && i<className.length()-1){
String parentClassName = className.substring(0,i);
try {
Class<?> tClass = Class.forName(parentClassName);
Class<?>[] innerClasses = tClass.getClasses();
if (innerClasses.length>0){
Class<?> matchingClass = null;
for (Class<?> innerClass:innerClasses){
if (innerClass.getCanonicalName().equals(className)){
matchingClass = innerClass;
break;
}
}
if (matchingClass != null){
Class<?> rClass = Class.forName("org.jhove2.core.reportable.Reportable");
if (rClass.isAssignableFrom(matchingClass)){
reportable = (Class<? extends Reportable>)matchingClass;
}
}
}
}
catch (ClassNotFoundException e1) {}
catch (SecurityException e2) {};
}
return reportable;
}
/**
* Utility method to construct full path to a file on class path. Used for example
* to locate DROID signature and configuration
* files. Assumes directory containing these files is on the classpath
* @param fileName File to be found on class path
* @param fileDescription descriptor of file to be used in any exception messages
* @return String containing path to file
* @throws JHOVE2Exception if file is not found or ClassLoader throws exception
*/
public static String getFilePathFromClasspath(String fileName, String fileDescription) throws JHOVE2Exception {
URI fileURI = null;
try {
fileURI = ClassLoader.getSystemResource(fileName).toURI();
if (fileURI == null){
throw new JHOVE2Exception(fileDescription + " " + fileName
+ " not found on classpath");
}
}
catch (URISyntaxException e){
throw new JHOVE2Exception("Exception thrown when attempting to find " + fileDescription
+ " on classpath", e);
}
String path = fileURI.getPath();
return path;
}
}