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
10package com.adobe.xmp.impl;
11
12import java.util.ArrayList;
13import java.util.Collections;
14import java.util.HashMap;
15import java.util.Iterator;
16import java.util.List;
17import java.util.Map;
18import java.util.TreeMap;
19import java.util.regex.Pattern;
20
21import com.adobe.xmp.XMPConst;
22import com.adobe.xmp.XMPError;
23import com.adobe.xmp.XMPException;
24import com.adobe.xmp.XMPSchemaRegistry;
25import com.adobe.xmp.options.AliasOptions;
26import 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 */
35public 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}