/**
* 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.traverser;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.jhove2.app.util.FeatureConfigurationUtil;
import org.jhove2.core.JHOVE2Exception;
import org.jhove2.core.reportable.info.ReportablePropertyInfo;
/**
* Traverses instance of a reportable object, collecting reportable feature information,
* including dotted path to reportable members
* Intended for use in generating "dotted names" for Assessment configuration
* NOTE: can only detect inner reportable classes to one level of nesting
* @author smorrissey
*
*/
public class ReportableInstanceTraverser {
public static final String USAGE =
"USAGE: java -cp CLASSPATH " + ReportableInstanceTraverser.class.getName()
+ " fully-qualified-class-name output-file-path {optional boolean should-recurse(default true)}";
/** Error return code for erroneous command line invocation */
public static final int EUSAGE = 1;
/** Error return code if any exception is thrown while executing program */
public static final int EEXCEPTION = 2;
/** Return code for successful execution */
public static final int SUCCESS = 0;
/** class name for instance */
protected String className;
/** feature information for this object (non-recursive)*/
protected SortedSet<PropertyDoc> reportablePropertiesInfo;
/** recursive feature information for this object */
protected SortedSet<PropertyDoc> allReportablePropertiesInfo;
/** should recursively collect feature information for features that are themselves of a Reportable type*/
protected boolean shouldRecurse;
/** base name for creating dotted names for reportable members */
protected String dottedBaseName;
public static void main(String[]args){
if (args.length<2){
System.out.println(USAGE);
System.exit(EUSAGE);
}
String clsName = args[0];
String outFileName = args[1];
boolean shdRecurs = true;
if (args.length>2){
String strShdRecurse =args[2];
if (strShdRecurse.toLowerCase().equals("true")){
shdRecurs = true;
}
else if (strShdRecurse.toLowerCase().equals("false")){
shdRecurs = false;
}
else {
System.out.println(USAGE);
System.exit(EUSAGE);
}
}
PrintWriter writer = null;
try {
writer = new PrintWriter(
new BufferedWriter(
new FileWriter(outFileName)));
} catch (IOException e) {
System.out.println("IOException creating file " + outFileName);
System.exit(EEXCEPTION);
}
ReportableInstanceTraverser rit = new ReportableInstanceTraverser(clsName,shdRecurs);
try {
rit.extractDocInfo();
for(PropertyDoc prop:rit.getAllReportablePropertiesInfo()){
writer.println(prop.dottedName + "\t " + prop.typeString);
}
writer.flush();
writer.close();
} catch (JHOVE2Exception e) {
System.out.println("Exception thrown processing class " + clsName);
e.printStackTrace();
System.exit(EEXCEPTION);
}
}
/**
* Constructor
* @param reportableClassName name of instance class
*/
public ReportableInstanceTraverser(String reportableClassName){
this(reportableClassName, false);
}
/**
* Constructor
* @param reportableClassName name of instance class
* @param shouldRecurse boolean should recursively collect feature information for features that are themselves of a Reportable type
*/
public ReportableInstanceTraverser(String reportableClassName, boolean shouldRecurse){
this(reportableClassName, shouldRecurse, reportableClassName.concat("."));
}
/**
* Constructor
* @param reportableClassName name of instance class
* @param shouldRecurse boolean should recursively collect feature information for features that are themselves of a Reportable type
* @param dottedBaseName base name for creating dotted names for reportable members
*/
public ReportableInstanceTraverser(String reportableClassName,
boolean shouldRecurse, String dottedBaseName){
this(reportableClassName, shouldRecurse, dottedBaseName, new TreeSet<PropertyDoc>());
}
/**
* Constructor
* @param reportableClassName name of instance class
* @param shouldRecurse boolean should recursively collect feature information for features that are themselves of a Reportable type
* @param dottedBaseName base name for creating dotted names for reportable members
* @param allReportablePropertiesInfo Set containing accumulated properties info
*/
public ReportableInstanceTraverser(String reportableClassName,
boolean shouldRecurse, String dottedBaseName, SortedSet<PropertyDoc> allReportablePropertiesInfo){
this.className = reportableClassName;
this.dottedBaseName = dottedBaseName;
this.shouldRecurse = shouldRecurse;
this.allReportablePropertiesInfo = allReportablePropertiesInfo;
}
/**
* Extract feature information about reportable object; recursively if so configured
* Entry point for getting all reportable instance information
* @return SortedSet<PropertyDoc> with reportable object information
* @throws JHOVE2Exception
*/
public SortedSet<PropertyDoc> extractDocInfo()
throws JHOVE2Exception{
this.reportablePropertiesInfo = this.extractReportablePropertiesInfo();
this.allReportablePropertiesInfo.addAll(this.reportablePropertiesInfo);
// now recurse over ReportableProperties
// if any of them are of type Reportable, get their info too
HashMap<String,String> dottedName2Classname;
if (this.reportablePropertiesInfo != null && this.shouldRecurse){
dottedName2Classname= new HashMap<String,String>();
for (PropertyDoc prop:reportablePropertiesInfo){
String dName = prop.dottedName.concat(".");
String pname= prop.type.getCanonicalName();
if (prop.type.isPrimitive()){
continue;
}
if (prop.type.isEnum()){
prop.typeString = pname;
continue;
}
if (prop.type.isArray()){
Class<?> arrayClass = prop.type.getComponentType();
if (FeatureConfigurationUtil.isReportableClass(arrayClass.getCanonicalName())){
int i = dName.lastIndexOf(".");
if (i>-1){
dName = dName.substring(0, i);
dName = dName.concat("[].");
}
dottedName2Classname.put(dName, arrayClass.getCanonicalName());
continue;
}
}
if (FeatureConfigurationUtil.isParameterizedType(prop.gType)){
ParameterizedType pType =(ParameterizedType)prop.gType;
for (Type type:pType.getActualTypeArguments()){
String typeClassName = type.toString();
if (typeClassName.startsWith("class ")){
typeClassName = typeClassName.substring("class ".length());
}
else if (typeClassName.startsWith("interface ")){
typeClassName = typeClassName.substring("interface ".length());
}
String paramType = prop.typeString;
int i = paramType.indexOf("<");
if (i>-1){
boolean shouldAddBrackets = false;
boolean shouldAddKV = false;
paramType = paramType.substring(0, i);
Class<?> tClass = null;
Class<?> cClass =null;
try {
tClass = Class.forName(paramType);
cClass = Class.forName("java.util.Collection");
if (cClass.isAssignableFrom(tClass)){
shouldAddBrackets = true;
}
else {
cClass = Class.forName("java.util.Map");
if (cClass.isAssignableFrom(tClass)){
shouldAddKV = true;
}
}
} catch (ClassNotFoundException e) {
;
}
int j = dName.lastIndexOf(".");
if (j>0){
if (shouldAddBrackets){
dName = dName.substring(0, j).concat("[].");
}
else if(shouldAddKV){
dName = dName.substring(0, j).concat("<K,V>.");
}
}
}// end if (i>-1)
if (FeatureConfigurationUtil.isReportableClass(typeClassName)){
dottedName2Classname.put(dName, typeClassName);
}
}
continue;
}
prop.typeString = pname;
if (FeatureConfigurationUtil.isReportableClass(pname)){
dottedName2Classname.put(dName, pname);
continue;
}
if (FeatureConfigurationUtil.isReportableInnerClass(pname)){
dottedName2Classname.put(dName, pname);
continue;
}
continue;
}
for (String dName:dottedName2Classname.keySet()){
String cName = dottedName2Classname.get(dName);
// avoid infinite loop
if (!cName.equals(this.className)){
ReportableInstanceTraverser rit = new ReportableInstanceTraverser(
cName,this.shouldRecurse,dName,this.allReportablePropertiesInfo);
SortedSet<PropertyDoc> newPd = rit.extractDocInfo();
this.allReportablePropertiesInfo.addAll(newPd);
}
}// end for (String dName:dottedName2Classname.keySet())
}//end if shouldRecurse
return this.allReportablePropertiesInfo;
}
/**
* Get ReportablePropertyInfo for this instance (non-recursive)
* @return SortedSet<PropertyDoc>
* @throws JHOVE2Exception
*/
protected SortedSet<PropertyDoc> extractReportablePropertiesInfo() throws JHOVE2Exception{
SortedSet<PropertyDoc>pdocs = new TreeSet<PropertyDoc>();
// get all reportable properties for this class and its superclasses
Set<ReportablePropertyInfo> rpis = FeatureConfigurationUtil.
getProperitiesAsReportablePropertyInfoSet(this.className);
for (ReportablePropertyInfo prop:rpis){
PropertyDoc pd = new PropertyDoc();
Method method = prop.getMethod();
pd.name = method.getName();
if (pd.name.indexOf("get") == 0) {
pd.name = pd.name.substring(3);
}
pd.id = prop.getIdentifier().getValue();
pd.typeString = prop.getGenericType().toString();
pd.type = prop.getMethod().getReturnType();
pd.gType = prop.getMethod().getGenericReturnType();
pd.desc = prop.getDescription();
if (pd.desc==null){
pd.desc = "";
}
pd.ref = prop.getReference();
if (pd.ref==null){
pd.ref = "";
}
String firstLetter = pd.name.substring(0,1).toLowerCase();
String fieldName = firstLetter;
if (pd.name.length()>1){
fieldName = fieldName + pd.name.substring(1);
}
pd.dottedName=this.dottedBaseName.concat(fieldName);
pdocs.add(pd);
}
return pdocs;
}
/**
* @return the className
*/
public String getClassName() {
return className;
}
/**
* @return the reportablePropertiesInfo
*/
public SortedSet<PropertyDoc> getReportablePropertiesInfo() {
return reportablePropertiesInfo;
}
/**
* @return the shouldRecurse
*/
public boolean isShouldRecurse() {
return shouldRecurse;
}
/**
* @return the dottedBaseName
*/
public String getDottedBaseName() {
return dottedBaseName;
}
/**
* @param className the className to set
*/
public void setClassName(String className) {
this.className = className;
}
/**
* @param reportablePropertiesInfo the reportablePropertiesInfo to set
*/
public void setReportablePropertiesInfo(
SortedSet<PropertyDoc> reportablePropertiesInfo) {
this.reportablePropertiesInfo = reportablePropertiesInfo;
}
/**
* @param shouldRecurse the shouldRecurse to set
*/
public void setShouldRecurse(boolean shouldRecurse) {
this.shouldRecurse = shouldRecurse;
}
/**
* @param dottedBaseName the dottedBaseName to set
*/
public void setDottedBaseName(String dottedBaseName) {
this.dottedBaseName = dottedBaseName;
}
/**
* @return the allReportablePropertiesInfo
*/
public SortedSet<PropertyDoc> getAllReportablePropertiesInfo() {
return allReportablePropertiesInfo;
}
/**
* @param allReportablePropertiesInfo the allReportablePropertiesInfo to set
*/
public void setAllReportablePropertiesInfo(
SortedSet<PropertyDoc> allReportablePropertiesInfo) {
this.allReportablePropertiesInfo = allReportablePropertiesInfo;
}
}