/**
* 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.module.display;
import static com.sleepycat.persist.model.DeleteAction.NULLIFY;
import static com.sleepycat.persist.model.Relationship.ONE_TO_ONE;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.jhove2.annotation.ReportableProperty.PropertyType;
import org.jhove2.config.ConfigInfo;
import org.jhove2.core.I8R;
import org.jhove2.core.JHOVE2Exception;
import org.jhove2.core.app.Application;
import org.jhove2.core.reportable.Reportable;
import org.jhove2.core.reportable.info.ReportableInfo;
import org.jhove2.core.reportable.info.ReportablePropertyInfo;
import org.jhove2.core.reportable.info.ReportableSourceInfo;
import org.jhove2.module.AbstractModule;
import org.jhove2.persist.DisplayerAccessor;
import org.jhove2.persist.ModuleAccessor;
import com.sleepycat.persist.model.Persistent;
import com.sleepycat.persist.model.SecondaryKey;
/**
* JHOVE2 displayer utility.
*
* @author mstrong, slabrams, smorrissey
*/
@Persistent
public abstract class AbstractDisplayer
extends AbstractModule
implements Displayer
{
/** foreign key linking Displayer to AbstractApplication */
@SecondaryKey(relate=ONE_TO_ONE, relatedEntity=AbstractModule.class,
onRelatedEntityDelete=NULLIFY)
protected Long parentAppId;
/** Feature display visibilities. */
public enum DisplayVisibility {
Always, IfFalse, IfNegative, IfNonNegative, IfNonPositive, IfNonZero, IfPositive, IfTrue, IfZero, Never
}
/** Units of measure configured by the user. */
private static ConcurrentMap<String, String> units;
/** Displayer visibility flags configured by user to indicate whether
* or not a feature should be displayed.
*/
private static ConcurrentMap<String, DisplayVisibility> visibilities;
/** Character encoding. */
protected String characterEncoding;
/** Configuration. */
protected ConfigInfo configInfo;
/** Output file pathname, if System.out is not being used. */
protected String filePathname;
/** Indentation flag. If true, displayed output is indented to indicate
* subsidiary relationships.
*/
protected boolean shouldIndent;
/** Show descriptive properties flag: if true, show properties. */
protected boolean showDescriptiveProperties;
/**
* Show identifiers flag: if true, show identifiers in JSON and Text display
* mode.
*/
protected boolean showIdentifiers;
/** Show raw properties flag: if true, show properties. */
protected boolean showRawProperties;
/**
* Instantiate a new <code>AbstractDisplayer</code>.
*/
public AbstractDisplayer(){
this(null);
}
/**
* Instantiate a new <code>AbstractDisplayer</code>.
* @param moduleAccessor
* Abstract Displayer persistence manager
*/
public AbstractDisplayer (ModuleAccessor moduleAccessor){
this(null, null, null, moduleAccessor);
}
/**
* Instantiate a new <code>AbstractDisplayer</code>.
*
* @param version
* AbstractDisplayer version identifier
* @param date
* AbstractDisplayer build date
* @param rights
* AbstractDisplayer rights statement
* @param moduleAccessor
* Abstract Displayer persistence manager
*/
public AbstractDisplayer(String version, String date, String rights,
ModuleAccessor moduleAccessor) {
super(version, date, rights, Scope.Generic, moduleAccessor);
this.setCharacterEncoding(DEFAULT_CHARACTER_ENCODING);
this.setShowDescriptiveProperties(DEFAULT_SHOW_DESCRIPTIVE_PROPERTIES);
this.setShowRawProperties(DEFAULT_SHOW_RAW_PROPERTIES);
this.setShowIdentifiers(DEFAULT_SHOW_IDENTIFIERS);
}
/**
* Display {@link org.jhove2.core.reportable.Reportable} to the standard output stream.
*
* @param reportable
* Reportable
* @throws FileNotFoundException
* Can't create output file
* @throws JHOVE2Exception
* Can't instantiate displayer
*/
@Override
public void display(Reportable reportable)
throws FileNotFoundException, JHOVE2Exception, UnsupportedEncodingException
{
this.display(reportable, this.getFilePathname());
}
/**
* Display {@link org.jhove2.core.reportable.Reportable} to a named file.
*
* @param reportable
* Reportable
* @param filePathname
* Output file pathname
* @throws FileNotFoundException
* Can't create output file
* @throws JHOVE2Exception
* Can't instantiate displayer
* @throws UnsupportedEncodingException
*/
@Override
public void display(Reportable reportable, String filePathname)
throws FileNotFoundException, JHOVE2Exception, UnsupportedEncodingException
{
PrintStream out = System.out;
if (filePathname == null) {
out = new PrintStream(System.out, false, this.characterEncoding);
}
else {
this.filePathname = filePathname;
out = new PrintStream(filePathname, this.characterEncoding);
}
this.display(reportable, out);
}
/**
* Display {@link org.jhove2.core.reportable.Reportable} to a
* {@link java.io.PrintStream}.
*
* @param reportable
* Reportable
* @param out
* Print stream
* @throws JHOVE2Exception
* Can't instantiate displayer
*/
@Override
public void display(Reportable reportable, PrintStream out)
throws JHOVE2Exception
{
this.getTimerInfo().setStartTime();
Map<String, String> units = getUnits(this);
Map<String, DisplayVisibility> visibilities = getVisibilities(this);
this.startDisplay(out, 0);
this.display (out, reportable, 0, 0, units, visibilities);
this.endDisplay (out, 0);
this.getTimerInfo().setEndTime();
}
/**
* Display a {@link org.jhove2.core.reportable.Reportable}.
*
* @param out
* Print stream
* @param reportable
* Reportable
* @param level
* Nesting level
* @param order
* Ordinal position of this reportable with respect to its
* enclosing reportable or collection
* @param units Map of units of measure
* @param visibilities Map of display visibilities
*/
protected void display(PrintStream out, Reportable reportable, int level,
int order, Map<String, String> units,
Map<String, DisplayVisibility> visibilities)
throws JHOVE2Exception
{
this.display(out, reportable, level, order, true, units, visibilities);
}
/**
* Display a {@link org.jhove2.core.reportable.Reportable}.
*
* @param out
* Print stream
* @param reportable
* Reportable
* @param level
* Nesting level
* @param order
* Ordinal position of this reportable with respect to its
* enclosing reportable or collection
* @param shouldNestReportable
* boolean indicating new level of reportable hierarchy
* @param units Map of units of measure
* @param visibilities Map of display visibilities
*/
protected void display(PrintStream out, Reportable reportable, int level,
int order, boolean shouldNestReportable,
Map<String, String> units,
Map<String, DisplayVisibility> visibilities)
throws JHOVE2Exception
{
ReportableInfo reportableInfo = new ReportableInfo(reportable);
String name = reportableInfo.getName();
I8R identifier = reportableInfo.getIdentifier();
if (shouldNestReportable){
this.startReportable(out, level, name, identifier, order);
}
int or = 0;
List<ReportableSourceInfo> list = reportableInfo.getProperties();
for (ReportableSourceInfo source : list) {
Set<ReportablePropertyInfo> props = source.getProperties();
for (ReportablePropertyInfo prop : props) {
/* Check if descriptive or raw properties should be displayed. */
PropertyType type = prop.getPropertyType();
if ((type == PropertyType.Descriptive && !this.showDescriptiveProperties) ||
(type == PropertyType.Raw && !this.showRawProperties)) {
continue;
}
I8R id = prop.getIdentifier();
String ident = id.getValue();
/* Check if a displayer directive applies. */
DisplayVisibility visbility = visibilities.get(ident);
if (visbility != null && visbility == DisplayVisibility.Never) {
continue;
}
Method method = prop.getMethod();
String methodName = method.getName();
String propertyName = methodName;
if (propertyName.indexOf("get") == 0) {
propertyName = propertyName.substring(3);
}
try {
Object value = method.invoke(reportable);
if (value != null) {
if (visbility != null) {
if (value instanceof Boolean) {
boolean b = ((Boolean) value).booleanValue();
if (( b && visbility == DisplayVisibility.IfFalse) ||
(!b && visbility == DisplayVisibility.IfTrue)) {
continue;
}
} else if (value instanceof Number) {
double d = ((Number) value).doubleValue();
if ((d == 0.0 && visbility == DisplayVisibility.IfNonZero) ||
(d != 0.0 && visbility == DisplayVisibility.IfZero) ||
(d < 0.0 && visbility == DisplayVisibility.IfNonNegative) ||
(d > 0.0 && visbility == DisplayVisibility.IfNonPositive) ||
(d <= 0.0 && visbility == DisplayVisibility.IfPositive) ||
(d >= 0.0 && visbility == DisplayVisibility.IfNegative)) {
continue;
}
}
}
String unit = units.get(ident);
display(out, level, propertyName, id, value, or++, unit);
}
} catch (IllegalArgumentException e) {
throw new JHOVE2Exception(
"AbstractDisplayer.display(): IllegalArgumentException", e);
} catch (IllegalAccessException e) {
throw new JHOVE2Exception(
"AbstractDisplayer.display(): IllegalAccessException", e);
} catch (InvocationTargetException e) {
throw new JHOVE2Exception(
"AbstractDisplayer.display(): InvocationTargetException", e);
}
}
}
if (shouldNestReportable){
this.endReportable(out, level, name, identifier);
}
}
/**
* Display a {@link org.jhove2.annotation.ReportableProperty}.
*
* @param out
* Print stream
* @param level
* Nesting level
* @param name
* Property name
* @param identifier
* Property identifier
* @param value
* Property value
* @param order
* Ordinal position of this property with respect to its
* enclosing reportable or collection
* @param unit Unit of measure (optional, may be null)
*/
protected void display(PrintStream out, int level, String name,
I8R identifier, Object value, int order, String unit)
throws JHOVE2Exception
{
if (value instanceof List<?>) {
List<?> valueList = (List<?>) value;
int size = valueList.size();
if (size > 0) {
this.startCollection(out, level + 1, name,
identifier, size, order);
String singularName = I8R.singularName(name);
I8R id = I8R.singularIdentifier(identifier);
int i = 0;
for (Object prop : valueList) {
this.display(out, level + 1, singularName, id, prop, i++, unit);
}
this.endCollection(out, level + 1, name, identifier, size);
}
}
else if (value instanceof Set<?>) {
Set<?> set = (Set<?>) value;
int size = set.size();
if (size > 0) {
this.startCollection(out, level + 1, name,
identifier, size, order);
String singularName = I8R.singularName(name);
I8R id = I8R.singularIdentifier(identifier);
int i = 0;
for (Object prop : set) {
display(out, level + 1, singularName, id, prop, i++, unit);
}
this.endCollection(out, level + 1, name, identifier, size);
}
}
else if (value instanceof Reportable) {
this.startReportable(out, level + 1, name, identifier,
order, ((Reportable)value).getReportableIdentifier());
display(out, (Reportable) value, level + 1, 0, false, units,
visibilities);
this.endReportable(out, level + 1, name, identifier);
}
else {
if (value instanceof Date) {
String date = ISO8601.format(value);
String dat1 = date.substring(0, 22);
String dat2 = date.substring(22);
value = dat1 + ":" + dat2;
}
this.displayProperty(out, level + 1, name, identifier, value,
order, unit);
}
}
/**
* Display property with no unit of measure.
*
* @param out
* Print stream
* @param level
* Nesting level
* @param name
* Property name
* @param identifier
* Property identifier in the JHOVE2 namespace
* @param value
* Property value
* @param order
* Ordinal position of this property with respect to its
* enclosing {@link org.jhove2.core.reportable.Reportable}
* or collection
*/
public void displayProperty(PrintStream out, int level, String name,
I8R identifier, Object value, int order) {
this.displayProperty(out, level, name, identifier, value, order, null);
}
/** Get the character encoding.
* @return Character encoding
*/
@Override
public String getCharacterEncoding() {
return this.characterEncoding;
}
/**
* @return the configInfo
*/
public ConfigInfo getConfigInfo() {
return configInfo;
}
/** Get output file pathname.
* @return Output file pathname
*/
@Override
public String getFilePathname() {
return this.filePathname;
}
/**
* Get indentation appropriate for a nesting level.
*
* @param level
* Nesting level
* @param shouldIndent
* Indentation flag; if true displayed output is indented to
* indicate subsidiarity relationships
* @return Indentation string
*/
public static String getIndent(int level, boolean shouldIndent) {
StringBuffer indent = new StringBuffer("");
if (shouldIndent){
for (int i = 0; i < level; i++) {
indent.append(" ");
}
}
return indent.toString();
}
/**
* Get indentation flag. If true, displayed output is indented to indicate
* subsidiarity relationships.
*
* @return Identation flag
*/
@Override
public boolean getShouldIndent() {
return this.shouldIndent;
}
/** Get show descriptive properties flag.
* @param Show descriptive properties flag: if true, show properties
* @see org.jhove2.module.display.Displayer#getShowDescriptiveProperties()
*/
public boolean getShowDescriptiveProperties() {
return this.showDescriptiveProperties;
}
/**
* Get show identifiers flag.
*
* @return Show identifiers flag; if true, show identifiers in JSON and Text
* display mode
* @see org.jhove2.module.display.Displayer#getShowIdentifiers()
*/
@Override
public boolean getShowIdentifiers() {
return this.showIdentifiers;
}
/** Get show raw properties flag.
* @param Show raw properties flag: if true, show properties
* @see org.jhove2.module.display.Displayer#getShowRawProperties()
*/
public boolean getShowRawProperties() {
return this.showRawProperties;
}
/** Utility method to get user-specified units of measure.
* @param displayer TODO
* @return TreeMap mapping from Reportable property I8R to a unit of measure
* @throws JHOVE2Exception
*/
public static ConcurrentMap<String, String> getUnits(Displayer displayer)
throws JHOVE2Exception
{
if (units == null){
units = new ConcurrentHashMap<String, String>();
Properties props = displayer.getConfigInfo().getProperties("DisplayUnits");
if (props != null) {
Set<String> keys = props.stringPropertyNames();
for (String key : keys) {
String value = props.getProperty(key);
if (value != null) {
units.put(key, value);
}
}
}
}
return units;
}
/**
* Utility method to get user-specified restrictions on display visibility of Reportable properties
* @param displayer TODO
* @return TreeMap mapping from Reportable property I8R to a DisplayVisibility
* @throws JHOVE2Exception
*/
public static ConcurrentMap<String, DisplayVisibility> getVisibilities(Displayer displayer)
throws JHOVE2Exception
{
if (visibilities == null){
visibilities = new ConcurrentHashMap<String, DisplayVisibility>();
Properties props = displayer.getConfigInfo().getProperties("DisplayVisibility");
if (props != null) {
Set<String> keys = props.stringPropertyNames();
for (String key : keys) {
/* Remove any trailing white space from the display directive. */
String prop = props.getProperty(key);
int in = prop.indexOf(' ');
if (in > 1) {
prop = prop.substring(0, in);
}
DisplayVisibility value =
DisplayVisibility.valueOf(prop);
if (value != null && value != DisplayVisibility.Always) {
visibilities.put(key, value);
}
}
}
}
return visibilities;
}
/** Set character encoding.
* @param encoding Character encoding
*/
public void setCharacterEncoding(String encoding) {
this.characterEncoding = encoding;
}
/**
* @param configInfo the configInfo to set
*/
public void setConfigInfo(ConfigInfo info) {
configInfo = info;
}
/** Set output file pathname
* @param filePathname Output file pathname
*/
@Override
public void setFilePathname(String filePathname) {
this.filePathname = filePathname;
}
/** Set show descriptive properties flag.
* @param flag If true, show descriptive properties
* @see org.jhove2.module.display.Displayer#setShowIdentifiers(boolean)
*/
@Override
public void setShowDescriptiveProperties(boolean flag) {
this.showDescriptiveProperties = flag;
}
/**
* Set show identifiers flag.
*
* @param flag
* If true, show identifiers in JSON and Text display mode
* @see org.jhove2.module.display.Displayer#setShowIdentifiers(boolean)
*/
@Override
public void setShowIdentifiers(boolean flag) {
this.showIdentifiers = flag;
}
/** Set show raw properties flag.
* @param flag If true, show raw properties
* @see org.jhove2.module.display.Displayer#setShowIdentifiers(boolean)
*/
@Override
public void setShowRawProperties(boolean flag) {
this.showRawProperties = flag;
}
/**
* Set indentation flag. If true, displayed output is indented to indicate
* subsidiarity relationships.
* @param shouldIndent Indentation flag
*/
@Override
public void setShouldIndent(boolean shouldIndent){
this.shouldIndent = shouldIndent;
}
@Override
public Long getParentAppId() {
return parentAppId;
}
@Override
public void setParentAppId(Long parentAppId) throws JHOVE2Exception {
Long oldId = this.parentAppId;
this.parentAppId = parentAppId;
this.getModuleAccessor().verifyNewParentModuleId(this, oldId, parentAppId);
}
@Override
public Application getParentApplication() throws JHOVE2Exception {
DisplayerAccessor da = null;
try {
ModuleAccessor ma = this.getModuleAccessor();
da = (DisplayerAccessor) ma;
}
catch (Exception e){
throw new JHOVE2Exception ("Failed to cast ModuleAccessor to DisplayerAccessor", e);
}
return da.getParentApplication(this);
}
}