/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 * 
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.openide.loaders;

import java.io.IOException;
import java.util.*;

import org.openide.*;
import org.openide.filesystems.*;

/** Loader for any kind of <code>MultiDataObject</code>. It provides
* support for recognition of a composite data object and registering
* entries into it.
*
* @author Jaroslav Tulach
*/
public abstract class MultiFileLoader extends DataLoader {
    static final long serialVersionUID=1521919955690157343L;


    /** Creates new multi file loader.
     * @param representationClass the representation class
    */
    protected MultiFileLoader (Class representationClass) {
        super (representationClass);
    }

    /** Creates new multi file loader.
    * @param representationClassName the fully qualified name of the
    * representation class.
    *
    * @since 1.10
    */
    protected MultiFileLoader (String representationClassName) {
        super (representationClassName);
    }

    /*  Provides standard implementation for recognizing files in the
    * loader. First of all the findEntry method is called to allow the
    * subclass to find right entry for the
    *
    * @param fo file object to recognize
    * @param recognized recognized files buffer.
    * @exception DataObjectExistsException if the data object for specific
    *    primary file already exists (thrown by constructor of DataObject)
    * @exception IOException if the object is recognized but cannot be created
    *
    * @return suitable data object or <CODE>null</CODE> if the handler cannot
    *   recognize this object (or its group)
    */
    protected final DataObject handleFindDataObject (
        FileObject fo, RecognizedFiles recognized ) throws IOException {
        // finds primary file for given file
        FileObject primary = findPrimaryFileImpl (fo);

        // if this loader does not recognizes this file => return
        if (primary == null) return null;
        
        if (ERR_WILL_LOG) {
            ERR.log(ErrorManager.INFORMATIONAL, getClass().getName() + " is accepting: " + fo); // NOI18N
        }

        MultiDataObject obj;
        try {
            // create the multi object
            obj = createMultiObject (primary);
            if (ERR_WILL_LOG) {
                ERR.log(ErrorManager.INFORMATIONAL, getClass().getName() + " created object for: " + fo + " obj: " + obj); // NOI18N
            }
        } catch (DataObjectExistsException ex) {
            // object already exists
            DataObject dataObject = ex.getDataObject ();
            if (ERR_WILL_LOG) {
                ERR.log(ErrorManager.INFORMATIONAL, getClass().getName() + " object already exists for: " + fo + " obj: " + dataObject); // NOI18N
            }
            
            if (dataObject.getLoader () != this) {
                if (ERR_WILL_LOG) {
                    ERR.log(ErrorManager.INFORMATIONAL, getClass().getName() + " loader is wrong: " + dataObject.getLoader().getClass().getName()); // NOI18N
                }
                // try to update the data object by allowing other 
                // loaders to take care of the object
                dataObject = checkCollision (dataObject, fo);
            }
            
            if (!(dataObject instanceof MultiDataObject)) {
                // but if it is not MultiDataObject, propadate the exception
                if (ERR_WILL_LOG) {
                    ERR.log(ErrorManager.INFORMATIONAL, getClass().getName() + " object is not MultiDataObject: " + dataObject); // NOI18N
                }
                throw ex;
            }
            obj = (MultiDataObject)dataObject;
        }

        if (obj.getLoader () != this) {
            if (ERR_WILL_LOG) {
                ERR.log(ErrorManager.INFORMATIONAL, getClass().getName() + " wrong loader: " + obj.getLoader().getClass().getName()); // NOI18N
            }
            // this primary file is recognized by a different
            // loader. We should not add entries to it
            return obj;
        }

        // mark all secondary entries used
        if (ERR_WILL_LOG) {
            ERR.log(ErrorManager.INFORMATIONAL, getClass().getName() + " marking secondary entries"); // NOI18N
        }
        obj.markSecondaryEntriesRecognized (recognized);

        // if the file is not between
        if (ERR_WILL_LOG) {
            ERR.log(ErrorManager.INFORMATIONAL, getClass().getName() + " register entry: " + fo); // NOI18N
        }
        org.openide.loaders.MultiDataObject.Entry e = obj.registerEntry (fo);
        if (ERR_WILL_LOG) {
            ERR.log(ErrorManager.INFORMATIONAL, getClass().getName() + " success: " + e); // NOI18N
        }

        return obj;
    }


    /** For a given file finds the primary file.
    * @param fo the (secondary) file
    *
    * @return the primary file for the file or <code>null</code> if the file is not
    *   recognized by this loader
    */
    protected abstract FileObject findPrimaryFile (FileObject fo);

    /** Creates the right data object for a given primary file.
    * It is guaranteed that the provided file will actually be the primary file
    * returned by {@link #findPrimaryFile}.
    *
    * @param primaryFile the primary file
    * @return the data object for this file
    * @exception DataObjectExistsException if the primary file already has a data object
    */
    protected abstract MultiDataObject createMultiObject (FileObject primaryFile)
    throws DataObjectExistsException, IOException;

    /** Creates the right primary entry for a given primary file.
    *
    * @param obj requesting object
    * @param primaryFile primary file recognized by this loader
    * @return primary entry for that file
    */
    protected abstract MultiDataObject.Entry createPrimaryEntry (MultiDataObject obj, FileObject primaryFile);

    /** Creates a new secondary entry for a given file.
    * Note that separate entries must be created for every secondary
    * file within a given multi-file data object.
    *
    * @param obj requesting object
    * @param secondaryFile a secondary file
    * @return the entry
    */
    protected abstract MultiDataObject.Entry createSecondaryEntry (MultiDataObject obj, FileObject secondaryFile);

    /** Called when there is a collision between a data object that 
    * this loader tries to create and already existing one.
    * 
    * @param obj existing data object
    * @param file the original file that has been recognized by this loader
    *    as bellonging to obj data object
    * @return the data object created for this or null
    */
    DataObject checkCollision (DataObject obj, FileObject file) {
        /* JST: Make protected when necessary. Do not forget to
        * change UniFileDataLoader too.
        */
        FileObject primary = obj.getPrimaryFile ();
        
        /*Set refusing = */DataObjectPool.getPOOL().revalidate (
            new HashSet (Collections.singleton(primary))
        );
            // ok, the obj is discarded
        DataObject result = DataObjectPool.getPOOL().find (primary);
        return result;
    }
    
    /** Called when an entry of the data object is deleted.
    * 
    * @param obj the object to check
    */
    void checkConsistency (MultiDataObject obj) {
        /* JST: Make protected when necessary. Do not forget to
        * change UniFileDataLoader too.
        */
        FileObject primary = obj.getPrimaryFile ();
        if (primary.equals (findPrimaryFileImpl (primary))) {
            // ok recognized
            return;
        }
        
        // something is wrong the loader does not recognize the data 
        // object anymore
        try {
            obj.setValid (false);
        } catch (java.beans.PropertyVetoException ex) {
            // ignore
        }
    }
    
    
    
    /** Called before list of files belonging to a data object
    * is returned from MultiDataObject.files () method. This allows 
    * each loader to perform additional tests and update the set of
    * entries for given data object.
    * <P>
    * Current implementation scans all files in directory.
    * 
    * @param obj the object to test
    */
    void checkFiles (MultiDataObject obj) {
        /* JST: Make protected (and rename) when necessary. Do not forget to
        * change UniFileDataLoader too.
        */


        FileObject primary = obj.getPrimaryFile ();
        assert primary != null : "Object " + obj + " cannot have null primary file"; // NOI18N
        FileObject parent = primary.getParent ();
        assert parent != null : "Object " + obj + " cannot have null parent file"; // NOI18N

        FileObject[] arr = parent.getChildren ();
        for (int i = 0; i < arr.length; i++) {
            FileObject pf = findPrimaryFileImpl (arr[i]);

            if (pf == primary) {
                // this object could belong to this loader
                try {
                    // this will go thru regular process of looking for
                    // data object and register this file with the right (but not
                    // necessary this one) data object
                    DataObject.find (arr[i]);
                } catch (DataObjectNotFoundException ex) {
                    // ignore
                }
            }
        }
    }

    MultiDataObject.Entry createSecondaryEntryImpl (MultiDataObject obj, FileObject secondaryFile) {
        return createSecondaryEntry (obj, secondaryFile);
    }

    FileObject findPrimaryFileImpl (FileObject fo) {
        return findPrimaryFile (fo);
    }
}
