Home | History | Annotate | Download | only in impl
      1 // =================================================================================================
      2 // ADOBE SYSTEMS INCORPORATED
      3 // Copyright 2006 Adobe Systems Incorporated
      4 // All Rights Reserved
      5 //
      6 // NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
      7 // of the Adobe license agreement accompanying it.
      8 // =================================================================================================
      9 
     10 package com.adobe.xmp.impl;
     11 
     12 import java.util.ArrayList;
     13 import java.util.Collections;
     14 import java.util.HashMap;
     15 import java.util.Iterator;
     16 import java.util.List;
     17 import java.util.Map;
     18 import java.util.TreeMap;
     19 import java.util.regex.Pattern;
     20 
     21 import com.adobe.xmp.XMPConst;
     22 import com.adobe.xmp.XMPError;
     23 import com.adobe.xmp.XMPException;
     24 import com.adobe.xmp.XMPSchemaRegistry;
     25 import com.adobe.xmp.options.AliasOptions;
     26 import com.adobe.xmp.properties.XMPAliasInfo;
     27 
     28 
     29 /**
     30  * The schema registry handles the namespaces, aliases and global options for the XMP Toolkit. There
     31  * is only one single instance used by the toolkit.
     32  *
     33  * @since 27.01.2006
     34  */
     35 public final class XMPSchemaRegistryImpl implements XMPSchemaRegistry, XMPConst
     36 {
     37 	/** a map from a namespace URI to its registered prefix */
     38 	private Map namespaceToPrefixMap = new HashMap();
     39 
     40 	/** a map from a prefix to the associated namespace URI */
     41 	private Map prefixToNamespaceMap = new HashMap();
     42 
     43 	/** a map of all registered aliases.
     44 	 *  The map is a relationship from a qname to an <code>XMPAliasInfo</code>-object. */
     45 	private Map aliasMap = new HashMap();
     46 	/** The pattern that must not be contained in simple properties */
     47 	private Pattern p = Pattern.compile("[/*?\\[\\]]");
     48 
     49 
     50 	/**
     51 	 * Performs the initialisation of the registry with the default namespaces, aliases and global
     52 	 * options.
     53 	 */
     54 	public XMPSchemaRegistryImpl()
     55 	{
     56 		try
     57 		{
     58 			registerStandardNamespaces();
     59 			registerStandardAliases();
     60 		}
     61 		catch (XMPException e)
     62 		{
     63 			throw new RuntimeException("The XMPSchemaRegistry cannot be initialized!");
     64 		}
     65 	}
     66 
     67 
     68 	// ---------------------------------------------------------------------------------------------
     69 	// Namespace Functions
     70 
     71 
     72 	/**
     73 	 * @see XMPSchemaRegistry#registerNamespace(String, String)
     74 	 */
     75 	public synchronized String registerNamespace(String namespaceURI, String suggestedPrefix)
     76 			throws XMPException
     77 	{
     78 		ParameterAsserts.assertSchemaNS(namespaceURI);
     79 		ParameterAsserts.assertPrefix(suggestedPrefix);
     80 
     81 		if (suggestedPrefix.charAt(suggestedPrefix.length() - 1) != ':')
     82 		{
     83 			suggestedPrefix += ':';
     84 		}
     85 
     86 		if (!Utils.isXMLNameNS(suggestedPrefix.substring(0,
     87 				suggestedPrefix.length() - 1)))
     88 		{
     89 			throw new XMPException("The prefix is a bad XML name", XMPError.BADXML);
     90 		}
     91 
     92 		String registeredPrefix = (String) namespaceToPrefixMap.get(namespaceURI);
     93 		String registeredNS = (String) prefixToNamespaceMap.get(suggestedPrefix);
     94 		if (registeredPrefix != null)
     95 		{
     96 			// Return the actual prefix
     97 			return registeredPrefix;
     98 		}
     99 		else
    100 		{
    101 			if (registeredNS != null)
    102 			{
    103 				// the namespace is new, but the prefix is already engaged,
    104 				// we generate a new prefix out of the suggested
    105 				String generatedPrefix = suggestedPrefix;
    106 				for (int i = 1; prefixToNamespaceMap.containsKey(generatedPrefix); i++)
    107 				{
    108 					generatedPrefix = suggestedPrefix
    109 							.substring(0, suggestedPrefix.length() - 1)
    110 							+ "_" + i + "_:";
    111 				}
    112 				suggestedPrefix = generatedPrefix;
    113 			}
    114 			prefixToNamespaceMap.put(suggestedPrefix, namespaceURI);
    115 			namespaceToPrefixMap.put(namespaceURI, suggestedPrefix);
    116 
    117 			// Return the suggested prefix
    118 			return suggestedPrefix;
    119 		}
    120 	}
    121 
    122 
    123 	/**
    124 	 * @see XMPSchemaRegistry#deleteNamespace(String)
    125 	 */
    126 	public synchronized void deleteNamespace(String namespaceURI)
    127 	{
    128 		String prefixToDelete = getNamespacePrefix(namespaceURI);
    129 		if (prefixToDelete != null)
    130 		{
    131 			namespaceToPrefixMap.remove(namespaceURI);
    132 			prefixToNamespaceMap.remove(prefixToDelete);
    133 		}
    134 	}
    135 
    136 
    137 	/**
    138 	 * @see XMPSchemaRegistry#getNamespacePrefix(String)
    139 	 */
    140 	public synchronized String getNamespacePrefix(String namespaceURI)
    141 	{
    142 		return (String) namespaceToPrefixMap.get(namespaceURI);
    143 	}
    144 
    145 
    146 	/**
    147 	 * @see XMPSchemaRegistry#getNamespaceURI(String)
    148 	 */
    149 	public synchronized String getNamespaceURI(String namespacePrefix)
    150 	{
    151 		if (namespacePrefix != null  &&  !namespacePrefix.endsWith(":"))
    152 		{
    153 			namespacePrefix += ":";
    154 		}
    155 		return (String) prefixToNamespaceMap.get(namespacePrefix);
    156 	}
    157 
    158 
    159 	/**
    160 	 * @see XMPSchemaRegistry#getNamespaces()
    161 	 */
    162 	public synchronized Map getNamespaces()
    163 	{
    164 		return Collections.unmodifiableMap(new TreeMap(namespaceToPrefixMap));
    165 	}
    166 
    167 
    168 	/**
    169 	 * @see XMPSchemaRegistry#getPrefixes()
    170 	 */
    171 	public synchronized Map getPrefixes()
    172 	{
    173 		return Collections.unmodifiableMap(new TreeMap(prefixToNamespaceMap));
    174 	}
    175 
    176 
    177 	/**
    178 	 * Register the standard namespaces of schemas and types that are included in the XMP
    179 	 * Specification and some other Adobe private namespaces.
    180 	 * Note: This method is not lock because only called by the constructor.
    181 	 *
    182 	 * @throws XMPException Forwards processing exceptions
    183 	 */
    184 	private void registerStandardNamespaces() throws XMPException
    185 	{
    186 		// register standard namespaces
    187 		registerNamespace(NS_XML, "xml");
    188 		registerNamespace(NS_RDF, "rdf");
    189 		registerNamespace(NS_DC, "dc");
    190 		registerNamespace(NS_IPTCCORE, "Iptc4xmpCore");
    191 
    192 		// register Adobe standard namespaces
    193 		registerNamespace(NS_X, "x");
    194 		registerNamespace(NS_IX, "iX");
    195 
    196 		registerNamespace(NS_XMP, "xmp");
    197 		registerNamespace(NS_XMP_RIGHTS, "xmpRights");
    198 		registerNamespace(NS_XMP_MM, "xmpMM");
    199 		registerNamespace(NS_XMP_BJ, "xmpBJ");
    200 		registerNamespace(NS_XMP_NOTE, "xmpNote");
    201 
    202 		registerNamespace(NS_PDF, "pdf");
    203 		registerNamespace(NS_PDFX, "pdfx");
    204 		registerNamespace(NS_PDFX_ID, "pdfxid");
    205 		registerNamespace(NS_PDFA_SCHEMA, "pdfaSchema");
    206 		registerNamespace(NS_PDFA_PROPERTY, "pdfaProperty");
    207 		registerNamespace(NS_PDFA_TYPE, "pdfaType");
    208 		registerNamespace(NS_PDFA_FIELD, "pdfaField");
    209 		registerNamespace(NS_PDFA_ID, "pdfaid");
    210 		registerNamespace(NS_PDFA_EXTENSION, "pdfaExtension");
    211 		registerNamespace(NS_PHOTOSHOP, "photoshop");
    212 		registerNamespace(NS_PSALBUM, "album");
    213 		registerNamespace(NS_EXIF, "exif");
    214 		registerNamespace(NS_EXIF_AUX, "aux");
    215 		registerNamespace(NS_TIFF, "tiff");
    216 		registerNamespace(NS_PNG, "png");
    217 		registerNamespace(NS_JPEG, "jpeg");
    218 		registerNamespace(NS_JP2K, "jp2k");
    219 		registerNamespace(NS_CAMERARAW, "crs");
    220 		registerNamespace(NS_ADOBESTOCKPHOTO, "bmsp");
    221 		registerNamespace(NS_CREATOR_ATOM, "creatorAtom");
    222 		registerNamespace(NS_ASF, "asf");
    223 		registerNamespace(NS_WAV, "wav");
    224 
    225 		// register Adobe private namespaces
    226 		registerNamespace(NS_DM, "xmpDM");
    227 		registerNamespace(NS_TRANSIENT, "xmpx");
    228 
    229 		// register Adobe standard type namespaces
    230 		registerNamespace(TYPE_TEXT, "xmpT");
    231 		registerNamespace(TYPE_PAGEDFILE, "xmpTPg");
    232 		registerNamespace(TYPE_GRAPHICS, "xmpG");
    233 		registerNamespace(TYPE_IMAGE, "xmpGImg");
    234 		registerNamespace(TYPE_FONT, "stFNT");
    235 		registerNamespace(TYPE_DIMENSIONS, "stDim");
    236 		registerNamespace(TYPE_RESOURCEEVENT, "stEvt");
    237 		registerNamespace(TYPE_RESOURCEREF, "stRef");
    238 		registerNamespace(TYPE_ST_VERSION, "stVer");
    239 		registerNamespace(TYPE_ST_JOB, "stJob");
    240 		registerNamespace(TYPE_MANIFESTITEM, "stMfs");
    241 		registerNamespace(TYPE_IDENTIFIERQUAL, "xmpidq");
    242 	}
    243 
    244 
    245 
    246 	// ---------------------------------------------------------------------------------------------
    247 	// Alias Functions
    248 
    249 
    250 	/**
    251 	 * @see XMPSchemaRegistry#resolveAlias(String, String)
    252 	 */
    253 	public synchronized XMPAliasInfo resolveAlias(String aliasNS, String aliasProp)
    254 	{
    255 		String aliasPrefix = getNamespacePrefix(aliasNS);
    256 		if (aliasPrefix == null)
    257 		{
    258 			return null;
    259 		}
    260 
    261 		return (XMPAliasInfo) aliasMap.get(aliasPrefix + aliasProp);
    262 	}
    263 
    264 
    265 	/**
    266 	 * @see XMPSchemaRegistry#findAlias(java.lang.String)
    267 	 */
    268 	public synchronized XMPAliasInfo findAlias(String qname)
    269 	{
    270 		return (XMPAliasInfo) aliasMap.get(qname);
    271 	}
    272 
    273 
    274 	/**
    275 	 * @see XMPSchemaRegistry#findAliases(String)
    276 	 */
    277 	public synchronized XMPAliasInfo[] findAliases(String aliasNS)
    278 	{
    279 		String prefix = getNamespacePrefix(aliasNS);
    280 		List result = new ArrayList();
    281 		if (prefix != null)
    282 		{
    283 			for (Iterator it = aliasMap.keySet().iterator(); it.hasNext();)
    284 			{
    285 				String qname = (String) it.next();
    286 				if (qname.startsWith(prefix))
    287 				{
    288 					result.add(findAlias(qname));
    289 				}
    290 			}
    291 
    292 		}
    293 		return (XMPAliasInfo[]) result.toArray(new XMPAliasInfo[result.size()]);
    294 	}
    295 
    296 
    297 	/**
    298 	 * Associates an alias name with an actual name.
    299 	 * <p>
    300 	 * Define a alias mapping from one namespace/property to another. Both
    301 	 * property names must be simple names. An alias can be a direct mapping,
    302 	 * where the alias and actual have the same data type. It is also possible
    303 	 * to map a simple alias to an item in an array. This can either be to the
    304 	 * first item in the array, or to the 'x-default' item in an alt-text array.
    305 	 * Multiple alias names may map to the same actual, as long as the forms
    306 	 * match. It is a no-op to reregister an alias in an identical fashion.
    307 	 * Note: This method is not locking because only called by registerStandardAliases
    308 	 * which is only called by the constructor.
    309 	 * Note2: The method is only package-private so that it can be tested with unittests
    310 	 *
    311 	 * @param aliasNS
    312 	 *            The namespace URI for the alias. Must not be null or the empty
    313 	 *            string.
    314 	 * @param aliasProp
    315 	 *            The name of the alias. Must be a simple name, not null or the
    316 	 *            empty string and not a general path expression.
    317 	 * @param actualNS
    318 	 *            The namespace URI for the actual. Must not be null or the
    319 	 *            empty string.
    320 	 * @param actualProp
    321 	 *            The name of the actual. Must be a simple name, not null or the
    322 	 *            empty string and not a general path expression.
    323 	 * @param aliasForm
    324 	 *            Provides options for aliases for simple aliases to array
    325 	 *            items. This is needed to know what kind of array to create if
    326 	 *            set for the first time via the simple alias. Pass
    327 	 *            <code>XMP_NoOptions</code>, the default value, for all
    328 	 *            direct aliases regardless of whether the actual data type is
    329 	 *            an array or not (see {@link AliasOptions}).
    330 	 * @throws XMPException
    331 	 *             for inconsistant aliases.
    332 	 */
    333 	synchronized void registerAlias(String aliasNS, String aliasProp, final String actualNS,
    334 			final String actualProp, final AliasOptions aliasForm) throws XMPException
    335 	{
    336 		ParameterAsserts.assertSchemaNS(aliasNS);
    337 		ParameterAsserts.assertPropName(aliasProp);
    338 		ParameterAsserts.assertSchemaNS(actualNS);
    339 		ParameterAsserts.assertPropName(actualProp);
    340 
    341 		// Fix the alias options
    342 		final AliasOptions aliasOpts = aliasForm != null ?
    343 			new AliasOptions(XMPNodeUtils.verifySetOptions(
    344 				aliasForm.toPropertyOptions(), null).getOptions()) :
    345 			new AliasOptions();
    346 
    347 		if (p.matcher(aliasProp).find()  ||  p.matcher(actualProp).find())
    348 		{
    349 			throw new XMPException("Alias and actual property names must be simple",
    350 					XMPError.BADXPATH);
    351 		}
    352 
    353 		// check if both namespaces are registered
    354 		final String aliasPrefix = getNamespacePrefix(aliasNS);
    355 		final String actualPrefix = getNamespacePrefix(actualNS);
    356 		if (aliasPrefix == null)
    357 		{
    358 			throw new XMPException("Alias namespace is not registered", XMPError.BADSCHEMA);
    359 		}
    360 		else if (actualPrefix == null)
    361 		{
    362 			throw new XMPException("Actual namespace is not registered",
    363 				XMPError.BADSCHEMA);
    364 		}
    365 
    366 		String key = aliasPrefix + aliasProp;
    367 
    368 		// check if alias is already existing
    369 		if (aliasMap.containsKey(key))
    370 		{
    371 			throw new XMPException("Alias is already existing", XMPError.BADPARAM);
    372 		}
    373 		else if (aliasMap.containsKey(actualPrefix + actualProp))
    374 		{
    375 			throw new XMPException(
    376 					"Actual property is already an alias, use the base property",
    377 					XMPError.BADPARAM);
    378 		}
    379 
    380 		XMPAliasInfo aliasInfo = new XMPAliasInfo()
    381 		{
    382 			/**
    383 			 * @see XMPAliasInfo#getNamespace()
    384 			 */
    385 			public String getNamespace()
    386 			{
    387 				return actualNS;
    388 			}
    389 
    390 			/**
    391 			 * @see XMPAliasInfo#getPrefix()
    392 			 */
    393 			public String getPrefix()
    394 			{
    395 				return actualPrefix;
    396 			}
    397 
    398 			/**
    399 			 * @see XMPAliasInfo#getPropName()
    400 			 */
    401 			public String getPropName()
    402 			{
    403 				return actualProp;
    404 			}
    405 
    406 			/**
    407 			 * @see XMPAliasInfo#getAliasForm()
    408 			 */
    409 			public AliasOptions getAliasForm()
    410 			{
    411 				return aliasOpts;
    412 			}
    413 
    414 			public String toString()
    415 			{
    416 				return actualPrefix + actualProp + " NS(" + actualNS + "), FORM ("
    417 						+ getAliasForm() + ")";
    418 			}
    419 		};
    420 
    421 		aliasMap.put(key, aliasInfo);
    422 	}
    423 
    424 
    425 	/**
    426 	 * @see XMPSchemaRegistry#getAliases()
    427 	 */
    428 	public synchronized Map getAliases()
    429 	{
    430 		return Collections.unmodifiableMap(new TreeMap(aliasMap));
    431 	}
    432 
    433 
    434 	/**
    435 	 * Register the standard aliases.
    436 	 * Note: This method is not lock because only called by the constructor.
    437 	 *
    438 	 * @throws XMPException If the registrations of at least one alias fails.
    439 	 */
    440 	private void registerStandardAliases() throws XMPException
    441 	{
    442 		AliasOptions aliasToArrayOrdered = new AliasOptions().setArrayOrdered(true);
    443 		AliasOptions aliasToArrayAltText = new AliasOptions().setArrayAltText(true);
    444 
    445 
    446 		// Aliases from XMP to DC.
    447 		registerAlias(NS_XMP, "Author", NS_DC, "creator", aliasToArrayOrdered);
    448 		registerAlias(NS_XMP, "Authors", NS_DC, "creator", null);
    449 		registerAlias(NS_XMP, "Description", NS_DC, "description", null);
    450 		registerAlias(NS_XMP, "Format", NS_DC, "format", null);
    451 		registerAlias(NS_XMP, "Keywords", NS_DC, "subject", null);
    452 		registerAlias(NS_XMP, "Locale", NS_DC, "language", null);
    453 		registerAlias(NS_XMP, "Title", NS_DC, "title", null);
    454 		registerAlias(NS_XMP_RIGHTS, "Copyright", NS_DC, "rights", null);
    455 
    456 		// Aliases from PDF to DC and XMP.
    457 		registerAlias(NS_PDF, "Author", NS_DC, "creator", aliasToArrayOrdered);
    458 		registerAlias(NS_PDF, "BaseURL", NS_XMP, "BaseURL", null);
    459 		registerAlias(NS_PDF, "CreationDate", NS_XMP, "CreateDate", null);
    460 		registerAlias(NS_PDF, "Creator", NS_XMP, "CreatorTool", null);
    461 		registerAlias(NS_PDF, "ModDate", NS_XMP, "ModifyDate", null);
    462 		registerAlias(NS_PDF, "Subject", NS_DC, "description", aliasToArrayAltText);
    463 		registerAlias(NS_PDF, "Title", NS_DC, "title", aliasToArrayAltText);
    464 
    465 		// Aliases from PHOTOSHOP to DC and XMP.
    466 		registerAlias(NS_PHOTOSHOP, "Author", NS_DC, "creator", aliasToArrayOrdered);
    467 		registerAlias(NS_PHOTOSHOP, "Caption", NS_DC, "description", aliasToArrayAltText);
    468 		registerAlias(NS_PHOTOSHOP, "Copyright", NS_DC, "rights", aliasToArrayAltText);
    469 		registerAlias(NS_PHOTOSHOP, "Keywords", NS_DC, "subject", null);
    470 		registerAlias(NS_PHOTOSHOP, "Marked", NS_XMP_RIGHTS, "Marked", null);
    471 		registerAlias(NS_PHOTOSHOP, "Title", NS_DC, "title", aliasToArrayAltText);
    472 		registerAlias(NS_PHOTOSHOP, "WebStatement", NS_XMP_RIGHTS, "WebStatement", null);
    473 
    474 		// Aliases from TIFF and EXIF to DC and XMP.
    475 		registerAlias(NS_TIFF, "Artist", NS_DC, "creator", aliasToArrayOrdered);
    476 		registerAlias(NS_TIFF, "Copyright", NS_DC, "rights", null);
    477 		registerAlias(NS_TIFF, "DateTime", NS_XMP, "ModifyDate", null);
    478 		registerAlias(NS_TIFF, "ImageDescription", NS_DC, "description", null);
    479 		registerAlias(NS_TIFF, "Software", NS_XMP, "CreatorTool", null);
    480 
    481 		// Aliases from PNG (Acrobat ImageCapture) to DC and XMP.
    482 		registerAlias(NS_PNG, "Author", NS_DC, "creator", aliasToArrayOrdered);
    483 		registerAlias(NS_PNG, "Copyright", NS_DC, "rights", aliasToArrayAltText);
    484 		registerAlias(NS_PNG, "CreationTime", NS_XMP, "CreateDate", null);
    485 		registerAlias(NS_PNG, "Description", NS_DC, "description", aliasToArrayAltText);
    486 		registerAlias(NS_PNG, "ModificationTime", NS_XMP, "ModifyDate", null);
    487 		registerAlias(NS_PNG, "Software", NS_XMP, "CreatorTool", null);
    488 		registerAlias(NS_PNG, "Title", NS_DC, "title", aliasToArrayAltText);
    489 	}
    490 }