001 package org.rakeshv.xml.album;
002
003 import java.io.BufferedWriter;
004 import java.io.File;
005 import java.io.FileNotFoundException;
006 import java.io.FileOutputStream;
007 import java.io.FileWriter;
008 import java.io.IOException;
009 import java.io.PrintWriter;
010
011 import java.util.List;
012 import java.util.Map;
013 import java.util.Iterator;
014 import java.util.TreeMap;
015
016 import org.jdom.Document;
017 import org.jdom.Element;
018 import org.jdom.JDOMException;
019 import org.jdom.Namespace;
020 import org.jdom.input.SAXBuilder;
021 import org.jdom.output.XMLOutputter;
022
023 /**
024 * Photo album implementation. This class presents a java view of the
025 * specified album xml file. This class also contains methods
026 * for creating new album entries, and for deleted existing
027 * entries.
028 *
029 * <p>You can use the {@link #createPhotoAlbum( String )} method to
030 * create a new <code>photo album</code> specified by the fully
031 * qualified file name specified in the method call.</p>
032 *
033 * <p>© Copyright 2003 Rakesh Vidyadharan</p>
034 *
035 * @author Rakesh Vidyadharan 2003 October 31
036 * @version $Id: PhotoAlbum.java,v 1.3 2004/10/16 19:17:02 rakesh Exp $
037 */
038 public class PhotoAlbum extends Object
039 {
040 /**
041 * The fully qualified name of the album XML file.
042 */
043 private String fileName = "";
044
045 /**
046 * The <code>map</code> of {@link Album} records. This corresponds
047 * to all the <code>album</code> elements under the
048 * <code>albums</code> element in the photo album. The map is
049 * populated using the {@link Album#name} as the <code>key</code>.
050 */
051 private Map albums = null;
052
053 /**
054 * A reference to the album XML document.
055 */
056 private Document document = null;
057
058 /**
059 * The <code>root element</code> of the XML file.
060 */
061 private Element rootElement = null;
062
063 /**
064 * The <code>namespace</code> for the root element.
065 */
066 private Namespace namespace = null;
067
068 /**
069 * Default constructor. Cannot be directly called.
070 */
071 protected PhotoAlbum()
072 {
073 super();
074 albums = new TreeMap();
075 }
076
077 /**
078 * Just invoke {@link #PhotoAlbum( String, String )} with a value of
079 * <code>null</code> for the preferred SAX Parser to use.
080 *
081 * @param fileName - The fully qualified name of the photo
082 * album XML file.
083 * @throws AlbumException - If any JDOM/IOExceptions are encountered
084 * while processing the album XML file.
085 */
086 public PhotoAlbum( String fileName ) throws AlbumException
087 {
088 this( fileName, null );
089 }
090
091 /**
092 * Load the specified album XML file using the specified
093 * SAX Parser.
094 *
095 * @see #loadAlbums( String )
096 *
097 * @param fileName - The fully qualified name of the photo
098 * album XML file.
099 * @param saxParser - The full class name of the preferred
100 * SAX Parser to use.
101 * @throws AlbumException - If any JDOM/IOExceptions are encountered
102 * while processing the album XML file.
103 */
104 public PhotoAlbum( String fileName, String saxParser )
105 throws AlbumException
106 {
107 this.fileName = fileName;
108 loadAlbums( saxParser );
109 }
110
111 /**
112 * Load the album XML file ({@link #fileName}) using JDOM,
113 * and populate the {@link #albums} class field.
114 *
115 * @see #populateAlbums()
116 *
117 * @param saxParser - The full class name of the SAX Parser
118 * to use. If this is <code>null</code> the default JAXP SAX
119 * Parser will be used.
120 * @throws AlbumException - If any JDOM/IOExceptions are encountered
121 * while processing the album XML file.
122 */
123 private void loadAlbums( String saxParser ) throws AlbumException
124 {
125 albums = new TreeMap();
126
127 SAXBuilder builder = null;
128 if ( saxParser == null )
129 {
130 builder = new SAXBuilder();
131 }
132 else
133 {
134 builder = new SAXBuilder( saxParser );
135 }
136
137 try
138 {
139 document = builder.build( new File( fileName ) );
140 }
141 catch ( Exception ex )
142 {
143 throw new AlbumException( "Error accessing album " + fileName + ".", ex );
144 }
145
146 Element rootElement = document.getRootElement();
147 namespace = rootElement.getNamespace( rootElement.getNamespacePrefix() );
148
149 // Populate the list of albums
150 populateAlbums();
151 }
152
153 /**
154 * Walk through the JDOM tree, and populate the {@link #albums}
155 * <code>Map</code> of {@link Album} objects. Fetches all the
156 * <code>children</code> of the {@link #rootElement} root
157 * <code>Element</code> in the album XML file.
158 *
159 * @see #populateAlbum( Element )
160 */
161 private void populateAlbums()
162 {
163 List list = rootElement.getChildren( "album", namespace );
164 for ( Iterator iterator = list.iterator(); iterator.hasNext(); )
165 {
166 Element element = (Element) iterator.next();
167 Album album = populateAlbum( element );
168 albums.put( album.getName(), album );
169 }
170 }
171
172 /**
173 * Walk through the child elements of the specified <code>album</code>
174 * element, and create a corresponding {@link Album} bean.
175 *
176 * @see #populatePhoto( Element )
177 * @param element - The current album element.
178 * @return Album - The java bean that was created with the child
179 * elements of the specified album element.
180 */
181 private Album populateAlbum( Element element )
182 {
183 Album album = new Album( element.getAttributeValue( "name" ) );
184
185 List list = element.getChildren( "photo", namespace );
186 for ( Iterator iterator = list.iterator(); iterator.hasNext(); )
187 {
188 Element elem = (Element) iterator.next();
189 album.addPhoto( populatePhoto( element ) );
190 }
191
192 return album;
193 }
194
195 /**
196 * Create a {@link Photo} java bean instance with all the data
197 * from the specified <code>photo</code> element.
198 *
199 * @see #populateImage( Element )
200 * @param element - The current photo element.
201 * @return Photo - The java bean that was created with the child
202 * elements of the specified photo element.
203 */
204 private Photo populatePhoto( Element element )
205 {
206 String content = "";
207 Photo photo = new Photo( Integer.parseInt( element.getChildTextTrim( "photoId", namespace ) ) );
208
209 content = element.getChildTextTrim( "title", namespace );
210 if ( content != null )
211 {
212 photo.setTitle( content );
213 }
214
215 photo.setFullImage( populateImage( element.getChild( "fullImage", namespace ) ) );
216 photo.setThumbnail( populateImage( element.getChild( "thumbnail", namespace ) ) );
217
218 content = element.getChildTextTrim( "description", namespace );
219 if ( content != null )
220 {
221 photo.setDescription( content );
222 }
223
224 return photo;
225 }
226
227 /**
228 * Return a {@link Image} java bean with the data from the specified
229 * <code>Element</code> of type <code>image</code>.
230 *
231 * @param element - The current image element.
232 * @return Album - The java bean that was created with the child
233 * elements of the specified image element.
234 */
235 private Image populateImage( Element element )
236 {
237 String content = "";
238 Image image = new Image( element.getChildTextTrim( "fileLocation", namespace ) );
239
240 content = element.getChildTextTrim( "width", namespace );
241 image.setWidth( Double.parseDouble( content ) );
242
243 content = element.getChildTextTrim( "height", namespace );
244 image.setHeight( Double.parseDouble( content ) );
245
246 content = element.getChildTextTrim( "altText", namespace );
247 image.setAltText( content );
248
249 return image;
250 }
251
252 /**
253 * Add the specified {@link Album} java bean to the {@link #albums}
254 * map. This method checks to make sure that an entry with the same
255 * {@link Album#name} does not exist in the {@link #albums}
256 * <code>Map</code>. The specified {@link Album} object is added
257 * both to the {@link #albums} <code>Map</code>, as well as to the
258 * JDOM tree.
259 *
260 * <p><b>Note:</b>This method does not write the modified JDOM tree
261 * back to the album file. To save your modifications, please
262 * use the {@link #saveAlbums()} method.</p>
263 *
264 * @see #addAlbumToTree( Album )
265 *
266 * @param album - The java bean with all the information
267 * pertaining to a album entry in the photo album.
268 * @throws AlbumException - If the specified {@link Album} has an
269 * identical {@link Album#name} as an existing entry in the
270 * {@link #albums} <code>Map</code>.
271 */
272 public void addAlbum( Album album ) throws AlbumException
273 {
274 // Check for existing album entry with same nick
275 if ( albums.containsKey( album.getName() ) )
276 {
277 throw new AlbumException( "The name specified " + album.getName() + " is already in use." );
278 }
279
280 // Add the album to the albums map and the JDOM tree
281 albums.put( album.getName(), album );
282 addAlbumToTree( album );
283 }
284
285 /**
286 * Save the specified {@link Album} java bean that has been modified
287 * to the underlying JDOM tree. The easiest way to do this is to
288 * remove the existing <code>album element</code>, and add the
289 * modified {@link Album} bean as a new child element of the
290 * <code>albums</code> element using {@link #addAlbumToTree( Album )}.
291 *
292 * <p><b>Note:</b>This method does not write the modified JDOM tree
293 * back to the album file. To save your modifications, please
294 * use the {@link #saveAlbums()} method.</p>
295 *
296 * @see #addAlbumToTree( Album )
297 * @param album - The java bean with all the information
298 * pertaining to a album entry in the album.
299 */
300 public void modifyAlbum( Album album )
301 {
302 // Find the appropriate element in the JDOM tree
303 List children = rootElement.getChildren( "album", namespace );
304 boolean elementFound = false;
305 for ( Iterator iterator = children.iterator(); iterator.hasNext(); )
306 {
307 Element element = (Element) iterator.next();
308 if ( element.getChildTextTrim( "name", namespace ).equals( album.getName() ) )
309 {
310 boolean result = rootElement.removeContent( element );
311 addAlbumToTree( album );
312 elementFound = true;
313 }
314
315 if ( elementFound ) break;
316 }
317 }
318
319 /**
320 * Add the specified {@link Album} object to the JDOM tree as a
321 * <code>album</code> element.
322 *
323 * <p><b>Note:</b>This method does not write the modified JDOM tree
324 * back to the album file. To save your modifications, please
325 * use the {@link #saveAlbums()} method.</p>
326 *
327 * @param album - The java bean with all the information
328 * pertaining to a album entry in the album.
329 */
330 private void addAlbumToTree( Album album )
331 {
332 // Add the album to the JDOM tree
333 Element newAlbum = new Element( "album", namespace );
334 newAlbum.setAttribute( "name", album.getName() );
335
336 addPhotoToTree( newAlbum, album );
337
338 if ( album.getDescription() != null )
339 {
340 Element component = new Element( "description", namespace );
341 component.addContent( album.getDescription() );
342 newAlbum.addContent( component );
343 }
344
345 rootElement.addContent( newAlbum );
346 }
347
348 /**
349 * Add all the {@link Photo} beans in the {@link Album} specified
350 * to the <code>Element</code> passed in. Walk through the {@link
351 * Album#photos} <code>map</code> and add each instance to the
352 * XML tree.
353 *
354 * @param element - The XML element to which the photos are
355 * to be added.
356 * @param album - The album instance from which the photos
357 * are to be added.
358 */
359 private void addPhotoToTree( Element element, Album album )
360 {
361 for ( Iterator iterator = album.getPhotos().values().iterator(); iterator.hasNext(); )
362 {
363 Photo photo = (Photo) iterator.next();
364 Image fullImage = photo.getFullImage();
365 Image thumbnail = photo.getThumbnail();
366
367 Element photoElement = new Element( "photo", namespace );
368
369 // Add the photoId element
370 Element component = new Element( "photoId", namespace );
371 component.addContent( photo.getPhotoId().toString() );
372 photoElement.addContent( component );
373
374 // Add the title element
375 component = new Element( "title", namespace );
376 component.addContent( photo.getTitle() );
377 photoElement.addContent( component );
378
379 // Add the fullImage and thumbnail elements
380 addImageToTree( photoElement, "fullImage", fullImage );
381 addImageToTree( photoElement, "thumbnail", thumbnail );
382
383 element.addContent( component );
384 }
385 }
386
387 /**
388 * Add the specified {@link Image} object to the specified element
389 * as an element with the name specified.
390 *
391 * @param element - The parent element to which the image
392 * element is to be added.
393 * @param name - The name to use for the image element that
394 * will be added to the parent.
395 * @param image - The image java bean to be used to populate
396 * the image element children.
397 */
398 private void addImageToTree( Element element, String name,
399 Image image )
400 {
401 Element imageElement = new Element( name, namespace );
402
403 // Add the fileLocation element
404 Element component = new Element( "fileLocation", namespace );
405 component.addContent( image.getFileLocation() );
406 imageElement.addContent( component );
407
408 // Add the width element
409 component = new Element( "width", namespace );
410 component.addContent( String.valueOf( image.getWidth() ) );
411 imageElement.addContent( component );
412
413 // Add the height element
414 component = new Element( "height", namespace );
415 component.addContent( String.valueOf( image.getHeight() ) );
416 imageElement.addContent( component );
417
418 // Add the altText element
419 component = new Element( "altText", namespace );
420 component.addContent( image.getAltText() );
421 imageElement.addContent( component );
422
423 element.addContent( imageElement );
424 }
425
426 /**
427 * Remove the {@link Album}'s identified by the specified {@link
428 * Album#name}s. This method removes the {@link Album}'s from
429 * the {@link #albums} <code>Map</code> as well as the appropriate
430 * <code>element</code> from the underlying JDOM tree. This method
431 * just iterates through the array, and invokes {@link
432 * #deleteAlbum( String )} to delete each {@link Album} specified.
433 *
434 * @param names - The names of the album that are to
435 * be removed.
436 * @return boolean - Returns <code>true</code> if the name exists
437 * in the {@link #albums} <code>Map</code>, and <code>false</code>
438 * otherwise. A return value of <code>false</code> indicates that
439 * not all the specified albums could be deleted, as not all of
440 * them were valid.
441 */
442 public boolean deleteAlbums( String[] names )
443 {
444 boolean result = true;
445
446 for ( int i = 0; i < names.length; ++i )
447 {
448 boolean value = deleteAlbum( names[i] );
449 if ( ! value )
450 {
451 result = value;
452 }
453 }
454
455 return result;
456 }
457
458 /**
459 * Remove the {@link Album} identified by the specified {@link
460 * Album#name}. This method removes the {@link Album} from
461 * the {@link #albums} <code>Map</code> as well as the appropriate
462 * <code>element</code> from the underlying JDOM tree.
463 *
464 * @param name - The name of the album that is to be removed.
465 * @return boolean - Returns <code>true</code> if the name exists
466 * in the {@link #albums} <code>Map</code>, and <code>false</code>
467 * otherwise.
468 */
469 public boolean deleteAlbum( String name )
470 {
471 if ( ! albums.containsKey( name ) )
472 {
473 return false;
474 }
475
476 // Find the appropriate element in the JDOM tree
477 List children = rootElement.getChildren( "album", namespace );
478 for ( Iterator iterator = children.iterator(); iterator.hasNext(); )
479 {
480 Element element = (Element) iterator.next();
481 if ( element.getChildTextTrim( "name", namespace ).equals( name ) )
482 {
483 boolean result = rootElement.removeContent( element );
484 albums.remove( name );
485
486 break;
487 }
488 }
489
490 return true;
491 }
492
493 /**
494 * Write the JDOM tree back to the album file.
495 *
496 * @throws AlbumException - If any exceptions are encountered while
497 * serialising the JDOM tree to the album file.
498 */
499 public void saveAlbums() throws AlbumException
500 {
501 FileOutputStream out = null;
502 try
503 {
504 out = new FileOutputStream( fileName );
505 }
506 catch ( FileNotFoundException fnfex )
507 {
508 throw new AlbumException( "The album " + fileName + " could not be found.", fnfex );
509 }
510
511 XMLOutputter outputter = new XMLOutputter();
512 outputter.getFormat().setLineSeparator( "\n" );
513 outputter.getFormat().setIndent( " " );
514 try
515 {
516 outputter.output( document, out );
517 out.close();
518 }
519 catch ( IOException ioex )
520 {
521 throw new AlbumException( "Error while writing to album " + fileName + ".", ioex );
522 }
523 }
524
525 /**
526 * Just invokes {@link #createPhotoAlbum( String, String )} with a
527 * <code>null</code> value for the default SAX Parser.
528 *
529 * @param file - The fully qualified name of the file ie. the
530 * full path and name of file to use as the album.
531 * @throws AlbumException - If any exceptions are encountered while
532 * creating or writing to the specified file.
533 */
534 public static PhotoAlbum createPhotoAlbum( String file )
535 throws AlbumException
536 {
537 return createPhotoAlbum( file, null );
538 }
539
540 /**
541 * Create a new album using the fully qualified file name
542 * specified, and using the specified SAX Parser to load the XML
543 * file that has been created.
544 *
545 * @param file - The fully qualified name of the file ie. the
546 * full path and name of file to use as the album.
547 * @param saxParser - The full class name of the preferred
548 * SAX Parser. If this is <code>null</code> the default JAXP
549 * SAX Parser will be used.
550 * @throws AlbumException - If any exceptions are encountered while
551 * creating or writing to the specified file.
552 */
553 public static PhotoAlbum createPhotoAlbum( String file,
554 String saxParser ) throws AlbumException
555 {
556 PhotoAlbum photoAlbum = new PhotoAlbum();
557 photoAlbum.setFileName( file );
558
559 try
560 {
561 PrintWriter out = new PrintWriter(
562 new BufferedWriter( new FileWriter( file ) )
563 );
564 out.println( "<?xml version='1.0' encoding='UTF-8'?>" );
565 out.println( "<albums xmlns='http://src.rakeshv.org/xml/schema/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='http://src.rakeshv.org/xml/schema/ http://src.rakeshv.org/xml/schema/album.xsd'>" );
566 out.println( " <album>" );
567 out.println( " </album>" );
568 out.println( "</albums>" );
569 out.flush();
570 out.close();
571 }
572 catch ( IOException iex )
573 {
574 throw new AlbumException( "Error creating/writing new album " + file + ".", iex );
575 }
576
577 photoAlbum.loadAlbums( saxParser );
578
579 return photoAlbum;
580 }
581
582 /**
583 * Return the value/reference of the {@link #albums}.
584 *
585 * @return Map - The value/reference of albums.
586 */
587 public final Map getAlbums()
588 {
589 return albums;
590 }
591
592 /**
593 * Returns {@link #fileName}.
594 *
595 * @return String - The value/reference of/to fileName.
596 */
597 public final String getFileName()
598 {
599 return fileName;
600 }
601
602 /**
603 * Set {@link #fileName}.
604 *
605 * @param fileName - The value to set.
606 */
607 public final void setFileName( String fileName )
608 {
609 this.fileName = fileName;
610 }
611 }