org.jajuk.base.Collection.java Source code

Java tutorial

Introduction

Here is the source code for org.jajuk.base.Collection.java

Source

/*
 *  Jajuk
 *  Copyright (C) The Jajuk Team
 *  http://jajuk.info
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *  
 */
package org.jajuk.base;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.lang.StringUtils;
import org.jajuk.services.core.SessionService;
import org.jajuk.util.Conf;
import org.jajuk.util.Const;
import org.jajuk.util.Messages;
import org.jajuk.util.ReadOnlyIterator;
import org.jajuk.util.UpgradeManager;
import org.jajuk.util.UtilString;
import org.jajuk.util.UtilSystem;
import org.jajuk.util.error.JajukException;
import org.jajuk.util.log.Log;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Items root container.
 */
public final class Collection extends DefaultHandler {
    /** The Constant TAG_CLOSE_NEWLINE.  */
    private static final String TAG_CLOSE_NEWLINE = ">\n";
    /** The Constant TAB_CLOSE_TAG_START.  */
    private static final String TAB_CLOSE_TAG_START = "</";
    /** Self instance. */
    private static Collection coll = new Collection();
    private static long lTime;
    /** Current ItemManager manager. */
    private ItemManager manager;
    /** upgrade for track IDs. */
    private final Map<String, String> hmWrongRightTrackID = new HashMap<String, String>();
    /** upgrade for album IDs. */
    private final Map<String, String> hmWrongRightAlbumID = new HashMap<String, String>();
    /** upgrade for artist IDs. */
    private final Map<String, String> hmWrongRightArtistID = new HashMap<String, String>();
    /** upgrade for album-artists IDs. */
    private final Map<String, String> hmWrongRightAlbumArtistID = new HashMap<String, String>();
    /** upgrade for genre IDs. */
    private final Map<String, String> hmWrongRightGenreID = new HashMap<String, String>();
    /** upgrade for device IDs. */
    private final Map<String, String> hmWrongRightDeviceID = new HashMap<String, String>();
    /** upgrade for directory IDs. */
    private final Map<String, String> hmWrongRightDirectoryID = new HashMap<String, String>();
    /** upgrade for file IDs. */
    private final Map<String, String> hmWrongRightFileID = new HashMap<String, String>();
    /** upgrade for playlist IDs. */
    private final Map<String, String> hmWrongRightPlaylistFileID = new HashMap<String, String>();
    /** Conversion of types from Jajuk < 1.4 */
    private final static Map<String, String> CONVERSION;
    static {
        CONVERSION = new HashMap<String, String>(12);
        CONVERSION.put("0", "mp3");
        CONVERSION.put("1", "m3u");
        CONVERSION.put("2", "ogg");
        CONVERSION.put("3", "wav");
        CONVERSION.put("4", "au");
        CONVERSION.put("5", "flac");
        CONVERSION.put("6", "wma");
        CONVERSION.put("7", "aac");
        CONVERSION.put("8", "m4a");
        CONVERSION.put("9", "ram");
        CONVERSION.put("10", "mp2");
    }
    /** [Perf] flag used to accelerate conversion. */
    private boolean needCheckConversions = true;
    /** [PERF] Does the type has been checked once for ID computation change ? Indeed, we check only one element of each type to check if this computation changed for perfs. */
    private boolean needCheckID = false;

    // Constants value, use lower value for mist numerous items to parse
    /**
     * .
     */
    private enum Stage {
        STAGE_NONE,
        /** The Constant STAGE_FILES.  */
        STAGE_FILES,
        /** The Constant STAGE_DIRECTORIES.  */
        STAGE_DIRECTORIES,
        /** The Constant STAGE_TRACKS.  */
        STAGE_TRACKS,
        /** The Constant STAGE_ALBUMS.  */
        STAGE_ALBUMS,
        /** The Constant STAGE_ARTISTS.  */
        STAGE_ARTISTS,
        /** The Constant STAGE_GENRES.  */
        STAGE_GENRES,
        /** The Constant STAGE_PLAYLIST_FILES.  */
        STAGE_PLAYLIST_FILES,
        /** The Constant STAGE_PLAYLISTS.  */
        STAGE_PLAYLISTS,
        /** The Constant STAGE_TYPES.  */
        STAGE_TYPES,
        /** The Constant STAGE_DEVICES.  */
        STAGE_DEVICES,
        /** The Constant STAGE_YEARS.  */
        STAGE_YEARS,
        /** STAGE_ALBUM_ARTIST. */
        STAGE_ALBUM_ARTIST
    }

    /** *************************************************************************** [PERF] provide current stage (files, tracks...) used to optimize switch when parsing the collection ************************************************************************** */
    private Stage stage = Stage.STAGE_NONE;
    /** The Constant additionFormatter.  */
    private final DateFormat additionFormatter = UtilString.getAdditionDateFormatter();

    /**
     * Instance getter.
     *
     * @return the instance
     */
    public static Collection getInstance() {
        return coll;
    }

    /**
     * Hidden constructor.
     */
    private Collection() {
        super();
    }

    /**
     * Write current collection to collection file for persistence between
     * sessions. 
     *
     * @param collectionFile 
     *
     * @throws IOException Signals that an I/O exception has occurred.
     */
    public static synchronized void commit() throws IOException {
        long time = System.currentTimeMillis();
        String sCharset = Conf.getString(Const.CONF_COLLECTION_CHARSET);
        java.io.File out = SessionService
                .getConfFileByPath(Const.FILE_COLLECTION + "." + Const.FILE_SAVING_FILE_EXTENSION);
        final BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(out), sCharset),
                1000000);
        try {
            bw.write("<?xml version='1.0' encoding='" + sCharset + "'?>\n");
            bw.write("<" + Const.XML_COLLECTION + " " + Const.XML_VERSION + "='" + Const.JAJUK_VERSION + "'>\n");
            // Devices
            writeItemList(bw, DeviceManager.getInstance().toXML(), DeviceManager.getInstance().getDevices(),
                    DeviceManager.getInstance().getXMLTag(), 40);
            // Genres
            writeItemList(bw, GenreManager.getInstance().toXML(), GenreManager.getInstance().getGenres(),
                    GenreManager.getInstance().getXMLTag(), 40);
            // Artists
            writeItemList(bw, ArtistManager.getInstance().toXML(), ArtistManager.getInstance().getArtists(),
                    ArtistManager.getInstance().getXMLTag(), 40);
            // Album artists
            writeItemList(bw, AlbumArtistManager.getInstance().toXML(),
                    AlbumArtistManager.getInstance().getAlbumArtists(),
                    AlbumArtistManager.getInstance().getXMLTag(), 40);
            // Albums
            writeItemList(bw, AlbumManager.getInstance().toXML(), AlbumManager.getInstance().getAlbums(),
                    AlbumManager.getInstance().getXMLTag(), 40);
            // Years
            writeItemList(bw, YearManager.getInstance().toXML(), YearManager.getInstance().getYears(),
                    YearManager.getInstance().getXMLTag(), 40);
            // Tracks
            // Cannot use writeItemList() method as we have a bit of special handling inside the loop here
            TrackManager.getInstance().getLock().readLock().lock();
            try {
                ReadOnlyIterator<Track> tracks = TrackManager.getInstance().getTracksIterator();
                bw.write(TrackManager.getInstance().toXML());
                while (tracks.hasNext()) {
                    Track track = tracks.next();
                    // We clean up all orphan tracks
                    if (track.getFiles().size() > 0) {
                        bw.write(track.toXml());
                    }
                }
            } finally {
                TrackManager.getInstance().getLock().readLock().unlock();
            }
            writeString(bw, TrackManager.getInstance().getXMLTag(), 200);
            // Directories
            writeItemList(bw, DirectoryManager.getInstance().toXML(),
                    DirectoryManager.getInstance().getDirectories(), DirectoryManager.getInstance().getXMLTag(),
                    100);
            // Files
            writeItemList(bw, FileManager.getInstance().toXML(), FileManager.getInstance().getFiles(),
                    FileManager.getInstance().getXMLTag(), 200);
            // Playlists
            writeItemList(bw, PlaylistManager.getInstance().toXML(), PlaylistManager.getInstance().getPlaylists(),
                    PlaylistManager.getInstance().getXMLTag(), 200);
            // end of collection
            bw.write("</" + Const.XML_COLLECTION + TAG_CLOSE_NEWLINE);
            bw.flush();
        } finally {
            bw.close();
        }
        // Override initial file
        java.io.File finalFile = SessionService.getConfFileByPath(Const.FILE_COLLECTION);
        UtilSystem.saveFileWithRecoverySupport(finalFile);
        Log.debug("Collection commited in " + (System.currentTimeMillis() - time) + " ms");
    }

    /**
    * Write item list. 
    *
    * @param bw 
    * @param header 
    * @param items 
    * @param footer 
    * @param buffer 
    *
    * @throws IOException Signals that an I/O exception has occurred.
    */
    private static void writeItemList(BufferedWriter bw, String header, List<? extends Item> items, String footer,
            int buffer) throws IOException {
        bw.write(header);
        for (Item item : items) {
            bw.write(item.toXml());
        }
        writeString(bw, footer, buffer);
    }

    /**
     * Write string. 
     *
     * @param bw 
     * @param toWrite 
     * @param buffer 
     *
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private static void writeString(BufferedWriter bw, String toWrite, int buffer) throws IOException {
        StringBuilder sb = new StringBuilder(buffer);
        sb.append(TAB_CLOSE_TAG_START);
        sb.append(toWrite);
        sb.append(TAG_CLOSE_NEWLINE);
        bw.write(sb.toString());
    }

    /**
     * Parse collection.xml file and put all collection information into memory
     *
     * @param file 
     *
     * @throws SAXException the SAX exception
     * @throws ParserConfigurationException the parser configuration exception
     * @throws JajukException the jajuk exception
     * @throws IOException Signals that an I/O exception has occurred.
     */
    public static void load(File file)
            throws SAXException, ParserConfigurationException, JajukException, IOException {
        // If we load the regular collection.xml file, try to recover it from previous crash
        java.io.File regularFile = SessionService.getConfFileByPath(Const.FILE_COLLECTION);
        if (file.equals(regularFile)) {
            UtilSystem.recoverFileIfRequired(regularFile);
        }
        Log.debug("Loading: " + file.getName());
        if (!file.exists()) {
            throw new JajukException(5, file.toString());
        }
        lTime = System.currentTimeMillis();
        SAXParserFactory spf = SAXParserFactory.newInstance();
        spf.setValidating(false);
        spf.setNamespaceAware(false);
        // See http://xerces.apache.org/xerces-j/features.html for details
        spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
        spf.setFeature("http://xml.org/sax/features/string-interning", true);
        SAXParser saxParser = spf.newSAXParser();
        saxParser.parse(file.toURI().toURL().toString(), getInstance());
    }

    /**
     * Perform a collection clean up for logical items ( delete orphan data ) Note
     * that we don't cleanup genres up because we want to keep genres even without
     * associated tracks for ambiences for instance.
     */
    public static synchronized void cleanupLogical() {
        // Tracks cleanup
        TrackManager.getInstance().cleanup();
        // Artists cleanup
        ArtistManager.getInstance().cleanup();
        // Album-artist cleanup
        AlbumArtistManager.getInstance().cleanup();
        // albums cleanup
        AlbumManager.getInstance().cleanup();
        // years cleanup
        YearManager.getInstance().cleanup();
    }

    /**
     * Clear the full collection Note that we don't clear TypeManager as it is not
     * read from a file but filled programmatically.
     */
    public static synchronized void clearCollection() {
        TrackManager.getInstance().clear();
        GenreManager.getInstance().clear();
        ArtistManager.getInstance().clear();
        AlbumArtistManager.getInstance().clear();
        AlbumManager.getInstance().clear();
        YearManager.getInstance().clear();
        FileManager.getInstance().clear();
        DirectoryManager.getInstance().clear();
        PlaylistManager.getInstance().clear();
        DeviceManager.getInstance().clear();
    }

    /**
     * parsing warning.
     *
     * @param spe 
     * @throws SAXException the SAX exception
     */
    @Override
    @SuppressWarnings("ucd")
    //NOSONAR
    public void warning(SAXParseException spe) throws SAXException {
        throw new SAXException(Messages.getErrorMessage(5) + " / " + spe.getSystemId() + "/" + spe.getLineNumber()
                + "/" + spe.getColumnNumber() + " : " + spe.getMessage());
    }

    /**
     * parsing error.
     *
     * @param spe 
     * @throws SAXException the SAX exception
     */
    @Override
    @SuppressWarnings("ucd")
    public void error(SAXParseException spe) throws SAXException {
        throw new SAXException(Messages.getErrorMessage(5) + " / " + spe.getSystemId() + "/" + spe.getLineNumber()
                + "/" + spe.getColumnNumber() + " : " + spe.getMessage());
    }

    /**
     * parsing fatal error.
     *
     * @param spe 
     * @throws SAXException the SAX exception
     */
    @Override
    @SuppressWarnings("ucd")
    public void fatalError(SAXParseException spe) throws SAXException {
        throw new SAXException(Messages.getErrorMessage(5) + " / " + spe.getSystemId() + "/" + spe.getLineNumber()
                + "/" + spe.getColumnNumber() + " : " + spe.getMessage());
    }

    /**
     * Called at parsing start.
     */
    @Override
    @SuppressWarnings("ucd")
    public void startDocument() {
        Log.debug("Starting collection file parsing...");
    }

    /**
     * Called at parsing end.
     */
    @Override
    @SuppressWarnings("ucd")
    public void endDocument() {
        long l = (System.currentTimeMillis() - lTime);
        Log.debug("Collection file parsing done : " + l + " ms");
    }

    /**
     * Called when we start an element intern() method use policy : we use this
     * method when adding a new string into JVM that will probably be referenced
     * by several objects like the Genre ID that is referenced by many tracks. In
     * this case, all the String objects share the same char[]. On another hand,
     * it musn't be used for strings that have low probability to be used several
     * times (like raw names) as it uses a lot of CPU (equals() is called) and we
     * want startup to be as fast as possible. Note that the use of intern() save
     * around 1/4 of overall heap memory
     *
     * We use sax-interning for the main items sections (<styles> for ie). For all
     * raw items, we don't perform equals on item name but we compare the string
     * hashcode
     *
     * @param sUri 
     * @param s 
     * @param sQName 
     * @param attributes 
     *
     * @throws SAXException the SAX exception
     */
    @Override
    public void startElement(String sUri, String s, String sQName, Attributes attributes) throws SAXException {
        try {
            int idIndex = attributes.getIndex(Const.XML_ID);
            // [PERF] Manage top tags to set current stage. Manages 'properties'
            // tags as well
            if (idIndex == -1) {
                // Note that we compare string with '==' for performance reasons and it is safe here.
                if (Const.XML_DEVICES == sQName) { //NOSONAR
                    manager = DeviceManager.getInstance();
                    stage = Stage.STAGE_DEVICES;
                    needCheckID = true;
                } else if (Const.XML_ALBUMS == sQName) {//NOSONAR
                    manager = AlbumManager.getInstance();
                    stage = Stage.STAGE_ALBUMS;
                    needCheckID = true;
                } else if (Const.XML_ARTISTS == sQName) {//NOSONAR
                    manager = ArtistManager.getInstance();
                    stage = Stage.STAGE_ARTISTS;
                    needCheckID = true;
                } else if (Const.XML_ALBUM_ARTISTS == sQName) {//NOSONAR
                    manager = AlbumArtistManager.getInstance();
                    stage = Stage.STAGE_ALBUM_ARTIST;
                    needCheckID = true;
                } else if (Const.XML_DIRECTORIES == sQName) {//NOSONAR
                    manager = DirectoryManager.getInstance();
                    stage = Stage.STAGE_DIRECTORIES;
                    needCheckID = true;
                } else if (Const.XML_FILES == sQName) {//NOSONAR
                    manager = FileManager.getInstance();
                    stage = Stage.STAGE_FILES;
                    needCheckID = true;
                } else if (Const.XML_PLAYLISTS == sQName) {//NOSONAR
                    // This code is here for Jajuk < 1.6 compatibility
                    manager = PlaylistManager.getInstance();
                    stage = Stage.STAGE_PLAYLISTS;
                    needCheckID = true;
                } else if (Const.XML_PLAYLIST_FILES == sQName) {//NOSONAR
                    manager = PlaylistManager.getInstance();
                    stage = Stage.STAGE_PLAYLIST_FILES;
                    needCheckID = true;
                } else if (Const.XML_GENRES == sQName) {//NOSONAR
                    manager = GenreManager.getInstance();
                    stage = Stage.STAGE_GENRES;
                    needCheckID = true;
                } else if (Const.XML_TRACKS == sQName) {//NOSONAR
                    manager = TrackManager.getInstance();
                    stage = Stage.STAGE_TRACKS;
                    needCheckID = true;
                } else if (Const.XML_YEARS == sQName) {//NOSONAR
                    manager = YearManager.getInstance();
                    stage = Stage.STAGE_YEARS;
                    needCheckID = true;
                } else if (Const.XML_TYPES == sQName) {//NOSONAR
                    // This is here for pre-1.7 collections, after we don't commit types
                    // anymore (they are set programmatically)
                    manager = TypeManager.getInstance();
                    stage = Stage.STAGE_TYPES;
                    needCheckID = false;
                } else if (Const.XML_PROPERTY == sQName) {//NOSONAR
                    // A property description
                    boolean bCustom = Boolean
                            .parseBoolean(attributes.getValue(attributes.getIndex(Const.XML_CUSTOM)));
                    boolean bConstructor = Boolean
                            .parseBoolean(attributes.getValue(attributes.getIndex(Const.XML_CONSTRUCTOR)));
                    boolean bShouldBeDisplayed = Boolean
                            .parseBoolean(attributes.getValue(attributes.getIndex(Const.XML_VISIBLE)));
                    boolean bEditable = Boolean
                            .parseBoolean(attributes.getValue(attributes.getIndex(Const.XML_EDITABLE)));
                    boolean bUnique = Boolean
                            .parseBoolean(attributes.getValue(attributes.getIndex(Const.XML_UNIQUE)));
                    Class<?> cType = Class.forName(attributes.getValue(Const.XML_TYPE));
                    String sDefaultValue = attributes.getValue(Const.XML_DEFAULT_VALUE).intern();
                    Object oDefaultValue = null;
                    if (sDefaultValue != null && sDefaultValue.length() > 0) {
                        try {
                            // Date format has changed from 1.3 (only yyyyMMdd
                            // addition format is used)
                            // so an exception will be thrown when upgrading
                            // from 1.2
                            // we reset default value to "today"
                            oDefaultValue = UtilString.parse(sDefaultValue, cType);
                        } catch (ParseException e) {
                            oDefaultValue = new Date();
                        }
                    }
                    String sPropertyName = attributes.getValue(Const.XML_NAME).intern();
                    if (manager.getMetaInformation(sPropertyName) == null) {
                        PropertyMetaInformation meta = new PropertyMetaInformation(sPropertyName, bCustom,
                                bConstructor, bShouldBeDisplayed, bEditable, bUnique, cType, oDefaultValue);
                        // standard properties are already loaded
                        manager.registerProperty(meta);
                    }
                }
                if (Const.XML_PROPERTY == sQName) {//NOSONAR
                    Log.debug("Found property: " + attributes.getValue(Const.XML_NAME));
                } else {
                    Log.debug("Starting stage: '" + stage + "' with property: '" + sQName + "' manager: "
                            + (manager != null ? manager.getXMLTag() : "<null>"));
                }
            } else {
                // Manage elements themselves using a switch for performances
                switch (stage) {
                case STAGE_FILES:
                    handleFiles(attributes, idIndex);
                    break;
                case STAGE_DIRECTORIES:
                    handleDirectories(attributes, idIndex);
                    break;
                case STAGE_TRACKS:
                    handleTracks(attributes, idIndex);
                    break;
                case STAGE_ALBUMS:
                    handleAlbums(attributes, idIndex);
                    break;
                case STAGE_ARTISTS:
                    handleArtists(attributes, idIndex);
                    break;
                case STAGE_ALBUM_ARTIST:
                    handleAlbumArtists(attributes, idIndex);
                    break;
                case STAGE_GENRES:
                    handleGenres(attributes, idIndex);
                    break;
                case STAGE_PLAYLIST_FILES:
                    handlePlaylistFiles(attributes, idIndex);
                    break;
                case STAGE_DEVICES:
                    handleDevices(attributes, idIndex);
                    break;
                case STAGE_YEARS:
                    handleYears(attributes, idIndex);
                    break;
                case STAGE_TYPES:
                    Log.warn("Unexpected Stage: STAGE_TYPES");
                    break;
                default:
                    Log.warn("Unexpected Stage: " + stage);
                }
            }
        } catch (Throwable e) {//NOSONAR
            // Make sure to catch every issue here (including runtime exceptions) so we make sure to start
            // jajuk
            StringBuilder sAttributes = new StringBuilder();
            for (int i = 0; i < attributes.getLength(); i++) {
                sAttributes.append('\n').append(attributes.getQName(i)).append('=').append(attributes.getValue(i));
            }
            Log.error(5, sAttributes.toString(), e);
        }
    }

    /**
     * Handle files. 
     *
     * @param attributes 
     * @param idIndex 
     */
    private void handleFiles(Attributes attributes, int idIndex) {
        String sItemName = attributes.getValue(Const.XML_NAME);
        // Check file type is still registered, it can be
        // useful for ie if mplayer is no more available
        String ext = UtilSystem.getExtension(sItemName);
        Type type = TypeManager.getInstance().getTypeByExtension(ext);
        if (type == null) {
            return;
        }
        String sTrackId = attributes.getValue(Const.XML_TRACK).intern();
        // UPGRADE check if track Id is right
        if ((hmWrongRightTrackID.size() > 0) &&
        // replace wrong by right ID
                (hmWrongRightTrackID.containsKey(sTrackId))) {
            sTrackId = hmWrongRightTrackID.get(sTrackId);
        }
        Track track = TrackManager.getInstance().getTrackByID(sTrackId);
        String sParentID = attributes.getValue(Const.XML_DIRECTORY).intern();
        // UPGRADE check parent ID is right
        if ((hmWrongRightDirectoryID.size() > 0) &&
        // replace wrong by right ID
                (hmWrongRightDirectoryID.containsKey(sParentID))) {
            sParentID = hmWrongRightDirectoryID.get(sParentID);
        }
        Directory dParent = DirectoryManager.getInstance().getDirectoryByID(sParentID);
        if (dParent == null || track == null) { // more checkups
            return;
        }
        String size = attributes.getValue(Const.XML_SIZE);
        long lSize = 0;
        if (size != null) {
            lSize = Long.parseLong(size);
        }
        // Quality analyze, handle format problems (mainly for
        // upgrades)
        long lQuality = 0;
        try {
            String sQuality = attributes.getValue(Const.XML_QUALITY);
            if (sQuality != null) {
                lQuality = UtilString.fastLongParser(sQuality);
            }
        } catch (Exception e) {
            if (Log.isDebugEnabled()) {
                // wrong format
                Log.debug(Messages.getString("Error.137") + ":" + sItemName + " Value: "
                        + attributes.getValue(Const.XML_QUALITY) + " Error:" + e.getMessage());
            }
        }
        String sID = attributes.getValue(idIndex).intern();
        /*
         * UPGRADE test : if first element we check has the right ID, we avoid wasting time checking
         * others item one. If is is an upgrade, we force the check.We always check id in debug mode.
         */
        String sRightID = sID;
        if (needCheckID) {
            sRightID = FileManager.createID(sItemName, dParent).intern();
            if (sRightID == sID) { //NOSONAR
                needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
            } else {
                Log.debug("** Wrong file Id, upgraded: " + sItemName);
                hmWrongRightFileID.put(sID, sRightID);
            }
        }
        org.jajuk.base.File file = FileManager.getInstance().registerFile(sRightID, sItemName, dParent, track,
                lSize, lQuality);
        file.populateProperties(attributes);
    }

    /**
     * Handle directories. 
     *
     * @param attributes 
     * @param idIndex 
     */
    private void handleDirectories(Attributes attributes, int idIndex) {
        Directory dParent = null;
        // dParent = null;
        String sParentID = attributes.getValue(Const.XML_DIRECTORY_PARENT).intern();
        // UPGRADE
        if ((hmWrongRightDirectoryID.size() > 0) && (hmWrongRightDirectoryID.containsKey(sParentID))) {
            sParentID = hmWrongRightDirectoryID.get(sParentID);
        }
        // We use intern() here for performances
        if (sParentID != "-1") { //NOSONAR
            // Parent directory should be already referenced
            // because of order conservation
            dParent = DirectoryManager.getInstance().getDirectoryByID(sParentID);
            // check parent directory exists
            if (dParent == null) {
                return;
            }
        }
        String sDeviceID = attributes.getValue(Const.XML_DEVICE).intern();
        // take upgraded device ID if needed
        if ((hmWrongRightDeviceID.size() > 0) && (hmWrongRightDeviceID.containsKey(sDeviceID))) {
            sDeviceID = hmWrongRightDeviceID.get(sDeviceID);
        }
        Device device = DeviceManager.getInstance().getDeviceByID(sDeviceID);
        if (device == null) { // check device exists
            return;
        }
        String sItemName = attributes.getValue(Const.XML_NAME);
        String sID = attributes.getValue(idIndex).intern();
        // UPGRADE test
        String sRightID = sID;
        if (needCheckID) {
            sRightID = DirectoryManager.createID(sItemName, device, dParent).intern();
            if (sRightID == sID) {//NOSONAR
                needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
            } else {
                Log.debug("** Wrong directory Id, upgraded: " + sItemName);
                hmWrongRightDirectoryID.put(sID, sRightID);
            }
        }
        Directory directory = DirectoryManager.getInstance().registerDirectory(sRightID, sItemName, dParent,
                device);
        directory.populateProperties(attributes);
        // also remember top-level directories at the device
        if (dParent == null) {
            device.addDirectory(directory);
        }
    }

    /**
     * Handle tracks. 
     *
     * @param attributes 
     * @param idIndex 
     *
     * @throws ParseException the parse exception
     */
    private void handleTracks(Attributes attributes, int idIndex) throws ParseException {
        String sID = attributes.getValue(idIndex).intern();
        String sTrackName = attributes.getValue(Const.XML_TRACK_NAME);
        // album
        String sAlbumID = attributes.getValue(Const.XML_TRACK_ALBUM).intern();
        if ((hmWrongRightAlbumID.size() > 0) && (hmWrongRightAlbumID.containsKey(sAlbumID))) {
            sAlbumID = hmWrongRightAlbumID.get(sAlbumID);
        }
        Album album = AlbumManager.getInstance().getAlbumByID(sAlbumID);
        // Genre
        String sGenreID = attributes.getValue(Const.XML_TRACK_GENRE).intern();
        if ((hmWrongRightGenreID.size() > 0) && (hmWrongRightGenreID.containsKey(sGenreID))) {
            sGenreID = hmWrongRightGenreID.get(sGenreID);
        }
        Genre genre = GenreManager.getInstance().getGenreByID(sGenreID);
        // Year
        String sYearID = attributes.getValue(Const.XML_TRACK_YEAR).intern();
        Year year = YearManager.getInstance().getYearByID(sYearID);
        // For jajuk < 1.4
        if (year == null) {
            year = YearManager.getInstance().registerYear(sYearID, sYearID);
        }
        // Artist
        String sArtistID = attributes.getValue(Const.XML_TRACK_ARTIST).intern();
        if ((hmWrongRightArtistID.size() > 0) && (hmWrongRightArtistID.containsKey(sArtistID))) {
            sArtistID = hmWrongRightArtistID.get(sArtistID);
        }
        Artist artist = ArtistManager.getInstance().getArtistByID(sArtistID);
        // Album-artist (not a constructor level property)
        String sAlbumArtist = attributes.getValue(Const.XML_ALBUM_ARTIST);
        if (StringUtils.isNotBlank(sAlbumArtist)) {
            sAlbumArtist = sAlbumArtist.intern();
        }
        if ((hmWrongRightAlbumArtistID.size() > 0) && (hmWrongRightAlbumArtistID.containsKey(sAlbumArtist))) {
            sAlbumArtist = hmWrongRightAlbumArtistID.get(sAlbumArtist);
        }
        // Note that when upgrading from jajuk < 1.9, album artists field is alway null, call on the
        // next line always return null
        AlbumArtist albumArtist = AlbumArtistManager.getInstance().getAlbumArtistByID(sAlbumArtist);
        if (albumArtist == null) {
            // we force album artist to this default, a deep scan will be required to get actual values
            albumArtist = AlbumArtistManager.getInstance().registerAlbumArtist(Const.UNKNOWN_ARTIST);
        }
        // Length
        long length = UtilString.fastLongParser(attributes.getValue(Const.XML_TRACK_LENGTH));
        // Type
        String typeID = attributes.getValue(Const.XML_TYPE).intern();
        if (needCheckConversions) {
            if (CONVERSION.containsKey(typeID)) {
                typeID = CONVERSION.get(typeID);
            } else {
                needCheckConversions = false;
            }
        }
        Type type = TypeManager.getInstance().getTypeByID(typeID);
        // more checkups
        if (album == null || artist == null) {
            return;
        }
        if (genre == null || type == null) {
            return;
        }
        // Idem for order
        long lOrder = 0l;
        try {
            lOrder = UtilString.fastLongParser(attributes.getValue(Const.XML_TRACK_ORDER));
        } catch (Exception e) {
            if (Log.isDebugEnabled()) {
                // wrong format
                Log.debug(Messages.getString("Error.137") + ":" + sTrackName); // wrong
            }
        }
        // Idem for disc number
        long lDiscNumber = 0l;
        if (attributes.getValue(Const.XML_TRACK_DISC_NUMBER) != null) {
            try {
                lDiscNumber = UtilString.fastLongParser(attributes.getValue(Const.XML_TRACK_DISC_NUMBER));
            } catch (Exception e) {
                if (Log.isDebugEnabled()) {
                    // wrong format
                    Log.debug(Messages.getString("Error.137") + ":" + sTrackName);
                }
            }
        }
        // UPGRADE test
        String sRightID = sID;
        if (needCheckID) {
            sRightID = TrackManager
                    .createID(sTrackName, album, genre, artist, length, year, lOrder, type, lDiscNumber).intern();
            if (sRightID == sID) {//NOSONAR
                needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
            } else {
                Log.debug("** Wrong Track Id, upgraded: " + sTrackName);
                hmWrongRightTrackID.put(sID, sRightID);
            }
        }
        Track track = TrackManager.getInstance().registerTrack(sRightID, sTrackName, album, genre, artist, length,
                year, lOrder, type, lDiscNumber);
        TrackManager.getInstance().changeTrackRate(track,
                UtilString.fastLongParser(attributes.getValue(Const.XML_TRACK_RATE)));
        track.setHits(UtilString.fastLongParser(attributes.getValue(Const.XML_TRACK_HITS)));
        // only set discovery date if it is available in the file
        if (attributes.getValue(Const.XML_TRACK_DISCOVERY_DATE) != null) {
            // Date format should be OK
            Date dAdditionDate = additionFormatter.parse(attributes.getValue(Const.XML_TRACK_DISCOVERY_DATE));
            track.setDiscoveryDate(dAdditionDate);
        }
        String sComment = attributes.getValue(Const.XML_TRACK_COMMENT);
        if (sComment == null) {
            sComment = "";
        }
        track.setComment(sComment.intern());
        track.setAlbumArtist(albumArtist);
        track.populateProperties(attributes);
    }

    /**
     * Handle albums. 
     *
     * @param attributes 
     * @param idIndex 
     */
    private void handleAlbums(Attributes attributes, int idIndex) {
        String sID = attributes.getValue(idIndex).intern();
        String sItemName = attributes.getValue(Const.XML_NAME).intern();
        String sAttributeAlbumArtist = attributes.getValue(Const.XML_ALBUM_ARTIST);
        if (sAttributeAlbumArtist != null) {
            // Make sure to store the string into the String pool to save memory
            sAttributeAlbumArtist.intern();//NOSONAR
        }
        long lItemDiscID = 0;
        String sAttributeDiskId = attributes.getValue(Const.XML_ALBUM_DISC_ID);
        if (sAttributeDiskId != null) {
            lItemDiscID = Long.parseLong(sAttributeDiskId);
        }
        // UPGRADE test
        String sRightID = sID;
        if (needCheckID) {
            sRightID = AlbumManager.createID(sItemName, lItemDiscID).intern();
            if (sRightID == sID) {//NOSONAR
                needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
            } else {
                Log.debug("** Wrong album Id, upgraded: " + sItemName);
                hmWrongRightAlbumID.put(sID, sRightID);
            }
        }
        Album album = AlbumManager.getInstance().registerAlbum(sRightID, sItemName, lItemDiscID);
        if (album != null) {
            album.populateProperties(attributes);
        }
    }

    /**
     * Handle artists. 
     *
     * @param attributes 
     * @param idIndex 
     */
    private void handleArtists(Attributes attributes, int idIndex) {
        String sID = attributes.getValue(idIndex).intern();
        String sItemName = attributes.getValue(Const.XML_NAME).intern();
        // UPGRADE test
        String sRightID = sID;
        if (needCheckID) {
            sRightID = ItemManager.createID(sItemName).intern();
            if (sRightID == sID) {//NOSONAR
                needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
            } else {
                Log.debug("** Wrong artist Id, upgraded: " + sItemName);
                hmWrongRightArtistID.put(sID, sRightID);
            }
        }
        Artist artist = ArtistManager.getInstance().registerArtist(sRightID, sItemName);
        if (artist != null) {
            artist.populateProperties(attributes);
        }
    }

    /**
     * Handle genres. 
     *
     * @param attributes 
     * @param idIndex 
     */
    private void handleGenres(Attributes attributes, int idIndex) {
        String sID = attributes.getValue(idIndex).intern();
        String sItemName = attributes.getValue(Const.XML_NAME).intern();
        // UPGRADE test
        String sRightID = sID;
        if (needCheckID) {
            sRightID = ItemManager.createID(sItemName).intern();
            if (sRightID == sID) {//NOSONAR
                needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
            } else {
                Log.debug("** Wrong genre Id, upgraded: " + sItemName);
                hmWrongRightGenreID.put(sID, sRightID);
            }
        }
        Genre genre = GenreManager.getInstance().registerGenre(sRightID, sItemName);
        if (genre != null) {
            genre.populateProperties(attributes);
        }
    }

    /**
     * Handle playlist files. 
     *
     * @param attributes 
     * @param idIndex 
     */
    private void handlePlaylistFiles(Attributes attributes, int idIndex) {
        String sParentID = attributes.getValue(Const.XML_DIRECTORY).intern();
        // UPGRADE check parent ID is right
        if ((hmWrongRightDirectoryID.size() > 0) &&
        // replace wrong by right ID
                (hmWrongRightDirectoryID.containsKey(sParentID))) {
            sParentID = hmWrongRightDirectoryID.get(sParentID);
        }
        Directory dParent = DirectoryManager.getInstance().getDirectoryByID(sParentID);
        if (dParent == null) { // check directory is exists
            return;
        }
        String sID = attributes.getValue(idIndex).intern();
        String sItemName = attributes.getValue(Const.XML_NAME);
        // UPGRADE test
        String sRightID = sID;
        if (needCheckID) {
            sRightID = PlaylistManager.createID(sItemName, dParent).intern();
            if (sRightID == sID) {//NOSONAR
                needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
            } else {
                Log.debug("** Wrong playlist Id, upgraded: " + sItemName);
                hmWrongRightPlaylistFileID.put(sID, sRightID);
            }
        }
        Playlist plf = PlaylistManager.getInstance().registerPlaylistFile(sRightID, sItemName, dParent);
        if (plf != null) {
            plf.populateProperties(attributes);
        }
    }

    /**
     * Handle devices. 
     *
     * @param attributes 
     * @param idIndex 
     */
    private void handleDevices(Attributes attributes, int idIndex) {
        String sID = attributes.getValue(idIndex).intern();
        String sItemName = attributes.getValue(Const.XML_NAME);
        long lType = UtilString.fastLongParser(attributes.getValue(Const.XML_TYPE));
        Device.Type type = Device.Type.values()[(int) lType];
        // UPGRADE test
        String sRightID = sID;
        if (needCheckID) {
            sRightID = ItemManager.createID(sItemName).intern();
            if (sRightID == sID) {//NOSONAR
                needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
            } else {
                Log.debug("** Wrong device Id, upgraded: " + sItemName);
                hmWrongRightDeviceID.put(sID, sRightID);
            }
        }
        String sURL = attributes.getValue(Const.XML_URL);
        Device device = DeviceManager.getInstance().registerDevice(sRightID, sItemName, type, sURL);
        if (device != null) {
            device.populateProperties(attributes);
        }
    }

    /**
     * Handle years. 
     *
     * @param attributes 
     * @param idIndex 
     */
    private void handleYears(Attributes attributes, int idIndex) {
        String sID = attributes.getValue(idIndex).intern();
        String sItemName = attributes.getValue(Const.XML_NAME).intern();
        Year year = YearManager.getInstance().registerYear(sID, sItemName);
        if (year != null) {
            year.populateProperties(attributes);
        }
    }

    /**
     * Handle album artists.
     *
     * @param attributes 
     * @param idIndex 
     */
    private void handleAlbumArtists(Attributes attributes, int idIndex) {
        String sID = attributes.getValue(idIndex).intern();
        String sItemName = attributes.getValue(Const.XML_NAME).intern();
        AlbumArtist albumArtist = AlbumArtistManager.getInstance().registerAlbumArtist(sID, sItemName);
        if (albumArtist != null) {
            albumArtist.populateProperties(attributes);
        }
    }

    /**
     * Gets the hm wrong right file id.
     *
     * @return list of wrong file id (used by history)
     */
    public Map<String, String> getHmWrongRightFileID() {
        return hmWrongRightFileID;
    }

    /**
     * Gets the wrong right album i ds.
     *
     * @return the wrong right album i ds
     */
    public Map<String, String> getWrongRightAlbumIDs() {
        return this.hmWrongRightAlbumID;
    }
}