blob: a97de9911d05a6f4ac47c602d34ef2dae995b65a [file] [log] [blame]
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
// Copyright 2006 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
package com.adobe.xmp.impl;
import java.util.Calendar;
import java.util.Iterator;
import com.adobe.xmp.XMPConst;
import com.adobe.xmp.XMPDateTime;
import com.adobe.xmp.XMPError;
import com.adobe.xmp.XMPException;
import com.adobe.xmp.XMPIterator;
import com.adobe.xmp.XMPMeta;
import com.adobe.xmp.XMPPathFactory;
import com.adobe.xmp.XMPUtils;
import com.adobe.xmp.impl.xpath.XMPPath;
import com.adobe.xmp.impl.xpath.XMPPathParser;
import com.adobe.xmp.options.IteratorOptions;
import com.adobe.xmp.options.ParseOptions;
import com.adobe.xmp.options.PropertyOptions;
import com.adobe.xmp.properties.XMPProperty;
/**
* Implementation for {@link XMPMeta}.
*
* @since 17.02.2006
*/
public class XMPMetaImpl implements XMPMeta, XMPConst
{
/** Property values are Strings by default */
private static final int VALUE_STRING = 0;
/** */
private static final int VALUE_BOOLEAN = 1;
/** */
private static final int VALUE_INTEGER = 2;
/** */
private static final int VALUE_LONG = 3;
/** */
private static final int VALUE_DOUBLE = 4;
/** */
private static final int VALUE_DATE = 5;
/** */
private static final int VALUE_CALENDAR = 6;
/** */
private static final int VALUE_BASE64 = 7;
/** root of the metadata tree */
private XMPNode tree;
/** the xpacket processing instructions content */
private String packetHeader = null;
/**
* Constructor for an empty metadata object.
*/
public XMPMetaImpl()
{
// create root node
tree = new XMPNode(null, null, null);
}
/**
* Constructor for a cloned metadata tree.
*
* @param tree
* an prefilled metadata tree which fulfills all
* <code>XMPNode</code> contracts.
*/
public XMPMetaImpl(XMPNode tree)
{
this.tree = tree;
}
/**
* @see XMPMeta#appendArrayItem(String, String, PropertyOptions, String,
* PropertyOptions)
*/
public void appendArrayItem(String schemaNS, String arrayName, PropertyOptions arrayOptions,
String itemValue, PropertyOptions itemOptions) throws XMPException
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertArrayName(arrayName);
if (arrayOptions == null)
{
arrayOptions = new PropertyOptions();
}
if (!arrayOptions.isOnlyArrayOptions())
{
throw new XMPException("Only array form flags allowed for arrayOptions",
XMPError.BADOPTIONS);
}
// Check if array options are set correctly.
arrayOptions = XMPNodeUtils.verifySetOptions(arrayOptions, null);
// Locate or create the array. If it already exists, make sure the array
// form from the options
// parameter is compatible with the current state.
XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName);
// Just lookup, don't try to create.
XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null);
if (arrayNode != null)
{
// The array exists, make sure the form is compatible. Zero
// arrayForm means take what exists.
if (!arrayNode.getOptions().isArray())
{
throw new XMPException("The named property is not an array", XMPError.BADXPATH);
}
// if (arrayOptions != null && !arrayOptions.equalArrayTypes(arrayNode.getOptions()))
// {
// throw new XMPException("Mismatch of existing and specified array form", BADOPTIONS);
// }
}
else
{
// The array does not exist, try to create it.
if (arrayOptions.isArray())
{
arrayNode = XMPNodeUtils.findNode(tree, arrayPath, true, arrayOptions);
if (arrayNode == null)
{
throw new XMPException("Failure creating array node", XMPError.BADXPATH);
}
}
else
{
// array options missing
throw new XMPException("Explicit arrayOptions required to create new array",
XMPError.BADOPTIONS);
}
}
doSetArrayItem(arrayNode, ARRAY_LAST_ITEM, itemValue, itemOptions, true);
}
/**
* @see XMPMeta#appendArrayItem(String, String, String)
*/
public void appendArrayItem(String schemaNS, String arrayName, String itemValue)
throws XMPException
{
appendArrayItem(schemaNS, arrayName, null, itemValue, null);
}
/**
* @throws XMPException
* @see XMPMeta#countArrayItems(String, String)
*/
public int countArrayItems(String schemaNS, String arrayName) throws XMPException
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertArrayName(arrayName);
XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName);
XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null);
if (arrayNode == null)
{
return 0;
}
if (arrayNode.getOptions().isArray())
{
return arrayNode.getChildrenLength();
}
else
{
throw new XMPException("The named property is not an array", XMPError.BADXPATH);
}
}
/**
* @see XMPMeta#deleteArrayItem(String, String, int)
*/
public void deleteArrayItem(String schemaNS, String arrayName, int itemIndex)
{
try
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertArrayName(arrayName);
String itemPath = XMPPathFactory.composeArrayItemPath(arrayName, itemIndex);
deleteProperty(schemaNS, itemPath);
}
catch (XMPException e)
{
// EMPTY, exceptions are ignored within delete
}
}
/**
* @see XMPMeta#deleteProperty(String, String)
*/
public void deleteProperty(String schemaNS, String propName)
{
try
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertPropName(propName);
XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName);
XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, false, null);
if (propNode != null)
{
XMPNodeUtils.deleteNode(propNode);
}
}
catch (XMPException e)
{
// EMPTY, exceptions are ignored within delete
}
}
/**
* @see XMPMeta#deleteQualifier(String, String, String, String)
*/
public void deleteQualifier(String schemaNS, String propName, String qualNS, String qualName)
{
try
{
// Note: qualNS and qualName are checked inside composeQualfierPath
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertPropName(propName);
String qualPath = propName + XMPPathFactory.composeQualifierPath(qualNS, qualName);
deleteProperty(schemaNS, qualPath);
}
catch (XMPException e)
{
// EMPTY, exceptions within delete are ignored
}
}
/**
* @see XMPMeta#deleteStructField(String, String, String, String)
*/
public void deleteStructField(String schemaNS, String structName, String fieldNS,
String fieldName)
{
try
{
// fieldNS and fieldName are checked inside composeStructFieldPath
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertStructName(structName);
String fieldPath = structName
+ XMPPathFactory.composeStructFieldPath(fieldNS, fieldName);
deleteProperty(schemaNS, fieldPath);
}
catch (XMPException e)
{
// EMPTY, exceptions within delete are ignored
}
}
/**
* @see XMPMeta#doesPropertyExist(String, String)
*/
public boolean doesPropertyExist(String schemaNS, String propName)
{
try
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertPropName(propName);
XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName);
final XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, false, null);
return propNode != null;
}
catch (XMPException e)
{
return false;
}
}
/**
* @see XMPMeta#doesArrayItemExist(String, String, int)
*/
public boolean doesArrayItemExist(String schemaNS, String arrayName, int itemIndex)
{
try
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertArrayName(arrayName);
String path = XMPPathFactory.composeArrayItemPath(arrayName, itemIndex);
return doesPropertyExist(schemaNS, path);
}
catch (XMPException e)
{
return false;
}
}
/**
* @see XMPMeta#doesStructFieldExist(String, String, String, String)
*/
public boolean doesStructFieldExist(String schemaNS, String structName, String fieldNS,
String fieldName)
{
try
{
// fieldNS and fieldName are checked inside composeStructFieldPath()
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertStructName(structName);
String path = XMPPathFactory.composeStructFieldPath(fieldNS, fieldName);
return doesPropertyExist(schemaNS, structName + path);
}
catch (XMPException e)
{
return false;
}
}
/**
* @see XMPMeta#doesQualifierExist(String, String, String, String)
*/
public boolean doesQualifierExist(String schemaNS, String propName, String qualNS,
String qualName)
{
try
{
// qualNS and qualName are checked inside composeQualifierPath()
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertPropName(propName);
String path = XMPPathFactory.composeQualifierPath(qualNS, qualName);
return doesPropertyExist(schemaNS, propName + path);
}
catch (XMPException e)
{
return false;
}
}
/**
* @see XMPMeta#getArrayItem(String, String, int)
*/
public XMPProperty getArrayItem(String schemaNS, String arrayName, int itemIndex)
throws XMPException
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertArrayName(arrayName);
String itemPath = XMPPathFactory.composeArrayItemPath(arrayName, itemIndex);
return getProperty(schemaNS, itemPath);
}
/**
* @throws XMPException
* @see XMPMeta#getLocalizedText(String, String, String, String)
*/
public XMPProperty getLocalizedText(String schemaNS, String altTextName, String genericLang,
String specificLang) throws XMPException
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertArrayName(altTextName);
ParameterAsserts.assertSpecificLang(specificLang);
genericLang = genericLang != null ? Utils.normalizeLangValue(genericLang) : null;
specificLang = Utils.normalizeLangValue(specificLang);
XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, altTextName);
XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null);
if (arrayNode == null)
{
return null;
}
Object[] result = XMPNodeUtils.chooseLocalizedText(arrayNode, genericLang, specificLang);
int match = ((Integer) result[0]).intValue();
final XMPNode itemNode = (XMPNode) result[1];
if (match != XMPNodeUtils.CLT_NO_VALUES)
{
return new XMPProperty()
{
public Object getValue()
{
return itemNode.getValue();
}
public PropertyOptions getOptions()
{
return itemNode.getOptions();
}
public String getLanguage()
{
return itemNode.getQualifier(1).getValue();
}
public String toString()
{
return itemNode.getValue().toString();
}
};
}
else
{
return null;
}
}
/**
* @see XMPMeta#setLocalizedText(String, String, String, String, String,
* PropertyOptions)
*/
public void setLocalizedText(String schemaNS, String altTextName, String genericLang,
String specificLang, String itemValue, PropertyOptions options) throws XMPException
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertArrayName(altTextName);
ParameterAsserts.assertSpecificLang(specificLang);
genericLang = genericLang != null ? Utils.normalizeLangValue(genericLang) : null;
specificLang = Utils.normalizeLangValue(specificLang);
XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, altTextName);
// Find the array node and set the options if it was just created.
XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, true, new PropertyOptions(
PropertyOptions.ARRAY | PropertyOptions.ARRAY_ORDERED
| PropertyOptions.ARRAY_ALTERNATE | PropertyOptions.ARRAY_ALT_TEXT));
if (arrayNode == null)
{
throw new XMPException("Failed to find or create array node", XMPError.BADXPATH);
}
else if (!arrayNode.getOptions().isArrayAltText())
{
if (!arrayNode.hasChildren() && arrayNode.getOptions().isArrayAlternate())
{
arrayNode.getOptions().setArrayAltText(true);
}
else
{
throw new XMPException(
"Specified property is no alt-text array", XMPError.BADXPATH);
}
}
// Make sure the x-default item, if any, is first.
boolean haveXDefault = false;
XMPNode xdItem = null;
for (Iterator it = arrayNode.iterateChildren(); it.hasNext();)
{
XMPNode currItem = (XMPNode) it.next();
if (!currItem.hasQualifier()
|| !XMPConst.XML_LANG.equals(currItem.getQualifier(1).getName()))
{
throw new XMPException("Language qualifier must be first", XMPError.BADXPATH);
}
else if (XMPConst.X_DEFAULT.equals(currItem.getQualifier(1).getValue()))
{
xdItem = currItem;
haveXDefault = true;
break;
}
}
// Moves x-default to the beginning of the array
if (xdItem != null && arrayNode.getChildrenLength() > 1)
{
arrayNode.removeChild(xdItem);
arrayNode.addChild(1, xdItem);
}
// Find the appropriate item.
// chooseLocalizedText will make sure the array is a language
// alternative.
Object[] result = XMPNodeUtils.chooseLocalizedText(arrayNode, genericLang, specificLang);
int match = ((Integer) result[0]).intValue();
XMPNode itemNode = (XMPNode) result[1];
boolean specificXDefault = XMPConst.X_DEFAULT.equals(specificLang);
switch (match)
{
case XMPNodeUtils.CLT_NO_VALUES:
// Create the array items for the specificLang and x-default, with
// x-default first.
XMPNodeUtils.appendLangItem(arrayNode, XMPConst.X_DEFAULT, itemValue);
haveXDefault = true;
if (!specificXDefault)
{
XMPNodeUtils.appendLangItem(arrayNode, specificLang, itemValue);
}
break;
case XMPNodeUtils.CLT_SPECIFIC_MATCH:
if (!specificXDefault)
{
// Update the specific item, update x-default if it matches the
// old value.
if (haveXDefault && xdItem != itemNode && xdItem != null
&& xdItem.getValue().equals(itemNode.getValue()))
{
xdItem.setValue(itemValue);
}
// ! Do this after the x-default check!
itemNode.setValue(itemValue);
}
else
{
// Update all items whose values match the old x-default value.
assert haveXDefault && xdItem == itemNode;
for (Iterator it = arrayNode.iterateChildren(); it.hasNext();)
{
XMPNode currItem = (XMPNode) it.next();
if (currItem == xdItem
|| !currItem.getValue().equals(
xdItem != null ? xdItem.getValue() : null))
{
continue;
}
currItem.setValue(itemValue);
}
// And finally do the x-default item.
if (xdItem != null)
{
xdItem.setValue(itemValue);
}
}
break;
case XMPNodeUtils.CLT_SINGLE_GENERIC:
// Update the generic item, update x-default if it matches the old
// value.
if (haveXDefault && xdItem != itemNode && xdItem != null
&& xdItem.getValue().equals(itemNode.getValue()))
{
xdItem.setValue(itemValue);
}
itemNode.setValue(itemValue); // ! Do this after
// the x-default
// check!
break;
case XMPNodeUtils.CLT_MULTIPLE_GENERIC:
// Create the specific language, ignore x-default.
XMPNodeUtils.appendLangItem(arrayNode, specificLang, itemValue);
if (specificXDefault)
{
haveXDefault = true;
}
break;
case XMPNodeUtils.CLT_XDEFAULT:
// Create the specific language, update x-default if it was the only
// item.
if (xdItem != null && arrayNode.getChildrenLength() == 1)
{
xdItem.setValue(itemValue);
}
XMPNodeUtils.appendLangItem(arrayNode, specificLang, itemValue);
break;
case XMPNodeUtils.CLT_FIRST_ITEM:
// Create the specific language, don't add an x-default item.
XMPNodeUtils.appendLangItem(arrayNode, specificLang, itemValue);
if (specificXDefault)
{
haveXDefault = true;
}
break;
default:
// does not happen under normal circumstances
throw new XMPException("Unexpected result from ChooseLocalizedText",
XMPError.INTERNALFAILURE);
}
// Add an x-default at the front if needed.
if (!haveXDefault && arrayNode.getChildrenLength() == 1)
{
XMPNodeUtils.appendLangItem(arrayNode, XMPConst.X_DEFAULT, itemValue);
}
}
/**
* @see XMPMeta#setLocalizedText(String, String, String, String, String)
*/
public void setLocalizedText(String schemaNS, String altTextName, String genericLang,
String specificLang, String itemValue) throws XMPException
{
setLocalizedText(schemaNS, altTextName, genericLang, specificLang, itemValue, null);
}
/**
* @throws XMPException
* @see XMPMeta#getProperty(String, String)
*/
public XMPProperty getProperty(String schemaNS, String propName) throws XMPException
{
return getProperty(schemaNS, propName, VALUE_STRING);
}
/**
* Returns a property, but the result value can be requested. It can be one
* of {@link XMPMetaImpl#VALUE_STRING}, {@link XMPMetaImpl#VALUE_BOOLEAN},
* {@link XMPMetaImpl#VALUE_INTEGER}, {@link XMPMetaImpl#VALUE_LONG},
* {@link XMPMetaImpl#VALUE_DOUBLE}, {@link XMPMetaImpl#VALUE_DATE},
* {@link XMPMetaImpl#VALUE_CALENDAR}, {@link XMPMetaImpl#VALUE_BASE64}.
*
* @see XMPMeta#getProperty(String, String)
* @param schemaNS
* a schema namespace
* @param propName
* a property name or path
* @param valueType
* the type of the value, see VALUE_...
* @return Returns an <code>XMPProperty</code>
* @throws XMPException
* Collects any exception that occurs.
*/
protected XMPProperty getProperty(String schemaNS, String propName, int valueType)
throws XMPException
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertPropName(propName);
final XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName);
final XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, false, null);
if (propNode != null)
{
if (valueType != VALUE_STRING && propNode.getOptions().isCompositeProperty())
{
throw new XMPException("Property must be simple when a value type is requested",
XMPError.BADXPATH);
}
final Object value = evaluateNodeValue(valueType, propNode);
return new XMPProperty()
{
public Object getValue()
{
return value;
}
public PropertyOptions getOptions()
{
return propNode.getOptions();
}
public String getLanguage()
{
return null;
}
public String toString()
{
return value.toString();
}
};
}
else
{
return null;
}
}
/**
* Returns a property, but the result value can be requested.
*
* @see XMPMeta#getProperty(String, String)
* @param schemaNS
* a schema namespace
* @param propName
* a property name or path
* @param valueType
* the type of the value, see VALUE_...
* @return Returns the node value as an object according to the
* <code>valueType</code>.
* @throws XMPException
* Collects any exception that occurs.
*/
protected Object getPropertyObject(String schemaNS, String propName, int valueType)
throws XMPException
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertPropName(propName);
final XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName);
final XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, false, null);
if (propNode != null)
{
if (valueType != VALUE_STRING && propNode.getOptions().isCompositeProperty())
{
throw new XMPException("Property must be simple when a value type is requested",
XMPError.BADXPATH);
}
return evaluateNodeValue(valueType, propNode);
}
else
{
return null;
}
}
/**
* @see XMPMeta#getPropertyBoolean(String, String)
*/
public Boolean getPropertyBoolean(String schemaNS, String propName) throws XMPException
{
return (Boolean) getPropertyObject(schemaNS, propName, VALUE_BOOLEAN);
}
/**
* @throws XMPException
* @see XMPMeta#setPropertyBoolean(String, String, boolean, PropertyOptions)
*/
public void setPropertyBoolean(String schemaNS, String propName, boolean propValue,
PropertyOptions options) throws XMPException
{
setProperty(schemaNS, propName, propValue ? TRUESTR : FALSESTR, options);
}
/**
* @see XMPMeta#setPropertyBoolean(String, String, boolean)
*/
public void setPropertyBoolean(String schemaNS, String propName, boolean propValue)
throws XMPException
{
setProperty(schemaNS, propName, propValue ? TRUESTR : FALSESTR, null);
}
/**
* @see XMPMeta#getPropertyInteger(String, String)
*/
public Integer getPropertyInteger(String schemaNS, String propName) throws XMPException
{
return (Integer) getPropertyObject(schemaNS, propName, VALUE_INTEGER);
}
/**
* @see XMPMeta#setPropertyInteger(String, String, int, PropertyOptions)
*/
public void setPropertyInteger(String schemaNS, String propName, int propValue,
PropertyOptions options) throws XMPException
{
setProperty(schemaNS, propName, new Integer(propValue), options);
}
/**
* @see XMPMeta#setPropertyInteger(String, String, int)
*/
public void setPropertyInteger(String schemaNS, String propName, int propValue)
throws XMPException
{
setProperty(schemaNS, propName, new Integer(propValue), null);
}
/**
* @see XMPMeta#getPropertyLong(String, String)
*/
public Long getPropertyLong(String schemaNS, String propName) throws XMPException
{
return (Long) getPropertyObject(schemaNS, propName, VALUE_LONG);
}
/**
* @see XMPMeta#setPropertyLong(String, String, long, PropertyOptions)
*/
public void setPropertyLong(String schemaNS, String propName, long propValue,
PropertyOptions options) throws XMPException
{
setProperty(schemaNS, propName, new Long(propValue), options);
}
/**
* @see XMPMeta#setPropertyLong(String, String, long)
*/
public void setPropertyLong(String schemaNS, String propName, long propValue)
throws XMPException
{
setProperty(schemaNS, propName, new Long(propValue), null);
}
/**
* @see XMPMeta#getPropertyDouble(String, String)
*/
public Double getPropertyDouble(String schemaNS, String propName) throws XMPException
{
return (Double) getPropertyObject(schemaNS, propName, VALUE_DOUBLE);
}
/**
* @see XMPMeta#setPropertyDouble(String, String, double, PropertyOptions)
*/
public void setPropertyDouble(String schemaNS, String propName, double propValue,
PropertyOptions options) throws XMPException
{
setProperty(schemaNS, propName, new Double(propValue), options);
}
/**
* @see XMPMeta#setPropertyDouble(String, String, double)
*/
public void setPropertyDouble(String schemaNS, String propName, double propValue)
throws XMPException
{
setProperty(schemaNS, propName, new Double(propValue), null);
}
/**
* @see XMPMeta#getPropertyDate(String, String)
*/
public XMPDateTime getPropertyDate(String schemaNS, String propName) throws XMPException
{
return (XMPDateTime) getPropertyObject(schemaNS, propName, VALUE_DATE);
}
/**
* @see XMPMeta#setPropertyDate(String, String, XMPDateTime,
* PropertyOptions)
*/
public void setPropertyDate(String schemaNS, String propName, XMPDateTime propValue,
PropertyOptions options) throws XMPException
{
setProperty(schemaNS, propName, propValue, options);
}
/**
* @see XMPMeta#setPropertyDate(String, String, XMPDateTime)
*/
public void setPropertyDate(String schemaNS, String propName, XMPDateTime propValue)
throws XMPException
{
setProperty(schemaNS, propName, propValue, null);
}
/**
* @see XMPMeta#getPropertyCalendar(String, String)
*/
public Calendar getPropertyCalendar(String schemaNS, String propName) throws XMPException
{
return (Calendar) getPropertyObject(schemaNS, propName, VALUE_CALENDAR);
}
/**
* @see XMPMeta#setPropertyCalendar(String, String, Calendar,
* PropertyOptions)
*/
public void setPropertyCalendar(String schemaNS, String propName, Calendar propValue,
PropertyOptions options) throws XMPException
{
setProperty(schemaNS, propName, propValue, options);
}
/**
* @see XMPMeta#setPropertyCalendar(String, String, Calendar)
*/
public void setPropertyCalendar(String schemaNS, String propName, Calendar propValue)
throws XMPException
{
setProperty(schemaNS, propName, propValue, null);
}
/**
* @see XMPMeta#getPropertyBase64(String, String)
*/
public byte[] getPropertyBase64(String schemaNS, String propName) throws XMPException
{
return (byte[]) getPropertyObject(schemaNS, propName, VALUE_BASE64);
}
/**
* @see XMPMeta#getPropertyString(String, String)
*/
public String getPropertyString(String schemaNS, String propName) throws XMPException
{
return (String) getPropertyObject(schemaNS, propName, VALUE_STRING);
}
/**
* @see XMPMeta#setPropertyBase64(String, String, byte[], PropertyOptions)
*/
public void setPropertyBase64(String schemaNS, String propName, byte[] propValue,
PropertyOptions options) throws XMPException
{
setProperty(schemaNS, propName, propValue, options);
}
/**
* @see XMPMeta#setPropertyBase64(String, String, byte[])
*/
public void setPropertyBase64(String schemaNS, String propName, byte[] propValue)
throws XMPException
{
setProperty(schemaNS, propName, propValue, null);
}
/**
* @throws XMPException
* @see XMPMeta#getQualifier(String, String, String, String)
*/
public XMPProperty getQualifier(String schemaNS, String propName, String qualNS,
String qualName) throws XMPException
{
// qualNS and qualName are checked inside composeQualfierPath
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertPropName(propName);
String qualPath = propName + XMPPathFactory.composeQualifierPath(qualNS, qualName);
return getProperty(schemaNS, qualPath);
}
/**
* @see XMPMeta#getStructField(String, String, String, String)
*/
public XMPProperty getStructField(String schemaNS, String structName, String fieldNS,
String fieldName) throws XMPException
{
// fieldNS and fieldName are checked inside composeStructFieldPath
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertStructName(structName);
String fieldPath = structName + XMPPathFactory.composeStructFieldPath(fieldNS, fieldName);
return getProperty(schemaNS, fieldPath);
}
/**
* @throws XMPException
* @see XMPMeta#iterator()
*/
public XMPIterator iterator() throws XMPException
{
return iterator(null, null, null);
}
/**
* @see XMPMeta#iterator(IteratorOptions)
*/
public XMPIterator iterator(IteratorOptions options) throws XMPException
{
return iterator(null, null, options);
}
/**
* @see XMPMeta#iterator(String, String, IteratorOptions)
*/
public XMPIterator iterator(String schemaNS, String propName, IteratorOptions options)
throws XMPException
{
return new XMPIteratorImpl(this, schemaNS, propName, options);
}
/**
* @throws XMPException
* @see XMPMeta#setArrayItem(String, String, int, String, PropertyOptions)
*/
public void setArrayItem(String schemaNS, String arrayName, int itemIndex, String itemValue,
PropertyOptions options) throws XMPException
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertArrayName(arrayName);
// Just lookup, don't try to create.
XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName);
XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null);
if (arrayNode != null)
{
doSetArrayItem(arrayNode, itemIndex, itemValue, options, false);
}
else
{
throw new XMPException("Specified array does not exist", XMPError.BADXPATH);
}
}
/**
* @see XMPMeta#setArrayItem(String, String, int, String)
*/
public void setArrayItem(String schemaNS, String arrayName, int itemIndex, String itemValue)
throws XMPException
{
setArrayItem(schemaNS, arrayName, itemIndex, itemValue, null);
}
/**
* @throws XMPException
* @see XMPMeta#insertArrayItem(String, String, int, String,
* PropertyOptions)
*/
public void insertArrayItem(String schemaNS, String arrayName, int itemIndex, String itemValue,
PropertyOptions options) throws XMPException
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertArrayName(arrayName);
// Just lookup, don't try to create.
XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName);
XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null);
if (arrayNode != null)
{
doSetArrayItem(arrayNode, itemIndex, itemValue, options, true);
}
else
{
throw new XMPException("Specified array does not exist", XMPError.BADXPATH);
}
}
/**
* @see XMPMeta#insertArrayItem(String, String, int, String)
*/
public void insertArrayItem(String schemaNS, String arrayName, int itemIndex, String itemValue)
throws XMPException
{
insertArrayItem(schemaNS, arrayName, itemIndex, itemValue, null);
}
/**
* @throws XMPException
* @see XMPMeta#setProperty(String, String, Object, PropertyOptions)
*/
public void setProperty(String schemaNS, String propName, Object propValue,
PropertyOptions options) throws XMPException
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertPropName(propName);
options = XMPNodeUtils.verifySetOptions(options, propValue);
XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName);
XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, true, options);
if (propNode != null)
{
setNode(propNode, propValue, options, false);
}
else
{
throw new XMPException("Specified property does not exist", XMPError.BADXPATH);
}
}
/**
* @see XMPMeta#setProperty(String, String, Object)
*/
public void setProperty(String schemaNS, String propName, Object propValue) throws XMPException
{
setProperty(schemaNS, propName, propValue, null);
}
/**
* @throws XMPException
* @see XMPMeta#setQualifier(String, String, String, String, String,
* PropertyOptions)
*/
public void setQualifier(String schemaNS, String propName, String qualNS, String qualName,
String qualValue, PropertyOptions options) throws XMPException
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertPropName(propName);
if (!doesPropertyExist(schemaNS, propName))
{
throw new XMPException("Specified property does not exist!", XMPError.BADXPATH);
}
String qualPath = propName + XMPPathFactory.composeQualifierPath(qualNS, qualName);
setProperty(schemaNS, qualPath, qualValue, options);
}
/**
* @see XMPMeta#setQualifier(String, String, String, String, String)
*/
public void setQualifier(String schemaNS, String propName, String qualNS, String qualName,
String qualValue) throws XMPException
{
setQualifier(schemaNS, propName, qualNS, qualName, qualValue, null);
}
/**
* @see XMPMeta#setStructField(String, String, String, String, String,
* PropertyOptions)
*/
public void setStructField(String schemaNS, String structName, String fieldNS,
String fieldName, String fieldValue, PropertyOptions options) throws XMPException
{
ParameterAsserts.assertSchemaNS(schemaNS);
ParameterAsserts.assertStructName(structName);
String fieldPath = structName + XMPPathFactory.composeStructFieldPath(fieldNS, fieldName);
setProperty(schemaNS, fieldPath, fieldValue, options);
}
/**
* @see XMPMeta#setStructField(String, String, String, String, String)
*/
public void setStructField(String schemaNS, String structName, String fieldNS,
String fieldName, String fieldValue) throws XMPException
{
setStructField(schemaNS, structName, fieldNS, fieldName, fieldValue, null);
}
/**
* @see XMPMeta#getObjectName()
*/
public String getObjectName()
{
return tree.getName() != null ? tree.getName() : "";
}
/**
* @see XMPMeta#setObjectName(String)
*/
public void setObjectName(String name)
{
tree.setName(name);
}
/**
* @see XMPMeta#getPacketHeader()
*/
public String getPacketHeader()
{
return packetHeader;
}
/**
* Sets the packetHeader attributes, only used by the parser.
* @param packetHeader the processing instruction content
*/
public void setPacketHeader(String packetHeader)
{
this.packetHeader = packetHeader;
}
/**
* Performs a deep clone of the XMPMeta-object
*
* @see java.lang.Object#clone()
*/
public Object clone()
{
XMPNode clonedTree = (XMPNode) tree.clone();
return new XMPMetaImpl(clonedTree);
}
/**
* @see XMPMeta#dumpObject()
*/
public String dumpObject()
{
// renders tree recursively
return getRoot().dumpNode(true);
}
/**
* @see XMPMeta#sort()
*/
public void sort()
{
this.tree.sort();
}
/**
* @see XMPMeta#normalize(ParseOptions)
*/
public void normalize(ParseOptions options) throws XMPException
{
if (options == null)
{
options = new ParseOptions();
}
XMPNormalizer.process(this, options);
}
/**
* @return Returns the root node of the XMP tree.
*/
public XMPNode getRoot()
{
return tree;
}
// -------------------------------------------------------------------------------------
// private
/**
* Locate or create the item node and set the value. Note the index
* parameter is one-based! The index can be in the range [1..size + 1] or
* "last()", normalize it and check the insert flags. The order of the
* normalization checks is important. If the array is empty we end up with
* an index and location to set item size + 1.
*
* @param arrayNode an array node
* @param itemIndex the index where to insert the item
* @param itemValue the item value
* @param itemOptions the options for the new item
* @param insert insert oder overwrite at index position?
* @throws XMPException
*/
private void doSetArrayItem(XMPNode arrayNode, int itemIndex, String itemValue,
PropertyOptions itemOptions, boolean insert) throws XMPException
{
XMPNode itemNode = new XMPNode(ARRAY_ITEM_NAME, null);
itemOptions = XMPNodeUtils.verifySetOptions(itemOptions, itemValue);
// in insert mode the index after the last is allowed,
// even ARRAY_LAST_ITEM points to the index *after* the last.
int maxIndex = insert ? arrayNode.getChildrenLength() + 1 : arrayNode.getChildrenLength();
if (itemIndex == ARRAY_LAST_ITEM)
{
itemIndex = maxIndex;
}
if (1 <= itemIndex && itemIndex <= maxIndex)
{
if (!insert)
{
arrayNode.removeChild(itemIndex);
}
arrayNode.addChild(itemIndex, itemNode);
setNode(itemNode, itemValue, itemOptions, false);
}
else
{
throw new XMPException("Array index out of bounds", XMPError.BADINDEX);
}
}
/**
* The internals for setProperty() and related calls, used after the node is
* found or created.
*
* @param node
* the newly created node
* @param value
* the node value, can be <code>null</code>
* @param newOptions
* options for the new node, must not be <code>null</code>.
* @param deleteExisting flag if the existing value is to be overwritten
* @throws XMPException thrown if options and value do not correspond
*/
void setNode(XMPNode node, Object value, PropertyOptions newOptions, boolean deleteExisting)
throws XMPException
{
if (deleteExisting)
{
node.clear();
}
// its checked by setOptions(), if the merged result is a valid options set
node.getOptions().mergeWith(newOptions);
if (!node.getOptions().isCompositeProperty())
{
// This is setting the value of a leaf node.
XMPNodeUtils.setNodeValue(node, value);
}
else
{
if (value != null && value.toString().length() > 0)
{
throw new XMPException("Composite nodes can't have values", XMPError.BADXPATH);
}
node.removeChildren();
}
}
/**
* Evaluates a raw node value to the given value type, apply special
* conversions for defined types in XMP.
*
* @param valueType
* an int indicating the value type
* @param propNode
* the node containing the value
* @return Returns a literal value for the node.
* @throws XMPException
*/
private Object evaluateNodeValue(int valueType, final XMPNode propNode) throws XMPException
{
final Object value;
String rawValue = propNode.getValue();
switch (valueType)
{
case VALUE_BOOLEAN:
value = new Boolean(XMPUtils.convertToBoolean(rawValue));
break;
case VALUE_INTEGER:
value = new Integer(XMPUtils.convertToInteger(rawValue));
break;
case VALUE_LONG:
value = new Long(XMPUtils.convertToLong(rawValue));
break;
case VALUE_DOUBLE:
value = new Double(XMPUtils.convertToDouble(rawValue));
break;
case VALUE_DATE:
value = XMPUtils.convertToDate(rawValue);
break;
case VALUE_CALENDAR:
XMPDateTime dt = XMPUtils.convertToDate(rawValue);
value = dt.getCalendar();
break;
case VALUE_BASE64:
value = XMPUtils.decodeBase64(rawValue);
break;
case VALUE_STRING:
default:
// leaf values return empty string instead of null
// for the other cases the converter methods provides a "null"
// value.
// a default value can only occur if this method is made public.
value = rawValue != null || propNode.getOptions().isCompositeProperty() ? rawValue : "";
break;
}
return value;
}
}