All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.properties.PropertiesDataObject Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.properties;

import org.openide.util.UserCancelException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ObjectInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.logging.Logger;
import org.openide.cookies.OpenCookie;
import org.openide.cookies.SaveCookie;

import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataNode;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectExistsException;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.loaders.MultiDataObject;
import org.openide.nodes.Children;
import org.openide.nodes.CookieSet;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.WeakListeners;
import static java.util.logging.Level.FINER;
import org.openide.filesystems.MIMEResolver;


/**
 * Object that provides main functionality for properties data loader.
 * Represents set of .properties files with same basic name (name without locale postfix).
 *
 * @author Ian Formanek
 */
@MIMEResolver.ExtensionRegistration(
    displayName="#PropertiesResolver",
    extension="properties",
    mimeType="text/x-properties",
    position=120
)
public final class PropertiesDataObject extends MultiDataObject implements CookieSet.Factory {

    /** Generated Serialized Version UID. */
    static final long serialVersionUID = 4795737295255253334L;

    static final Logger LOG = Logger.getLogger(PropertiesDataObject.class.getName());

    /** Structural view of the dataobject */
    private transient BundleStructure bundleStructure;

    /** Open support for this data object. Provides editable table view on bundle. */
    private transient PropertiesOpen openSupport;

    /** Lock used for synchronization of openSupport instance creation */
    private final transient Object OPEN_SUPPORT_LOCK = new Object();

    // Hack due having lock on secondaries, can't override handleCopy, handleMove at all.
    /** Suffix used by copying/moving dataObject. */
    private transient String pasteSuffix;

    /** */
    private Lookup lookup;


    /**
     * Constructs a PropertiesDataObject for a specified
     * primary file.
     *
     * @param  primaryFile  primary file to creata a data object for
     * @param  loader  data loader which recognized the primary file
     * @exception   org.openide.loaders.DataObjectExistsException 
     *              if another DataObject already exists
     *              for the specified file
     */
    public PropertiesDataObject(final FileObject primaryFile,
            final PropertiesDataLoader loader)
            throws DataObjectExistsException {
        super(primaryFile, loader);
        // use editor support
        initialize();
    }

    /**
     */
    PropertiesEncoding getEncoding() {
        return ((PropertiesDataLoader) getLoader()).getEncoding();
    }

    @Override
    protected int associateLookup() {
        return 1;
    }

    /** Initializes the object. Used by construction and deserialized. */
    private void initialize() {
        bundleStructure = null;
        Class[] arr = (Class[]) new Class[2];
        arr[0] = PropertiesOpen.class;
        arr[1] = PropertiesEditorSupport.class;
        getCookieSet().add(arr, this);
        getCookieSet().assign(PropertiesEncoding.class, getEncoding());
    }

    /** Implements CookieSet.Factory interface method. */
    @SuppressWarnings("unchecked")
    public  T createCookie(Class clazz) {
        if(clazz.isAssignableFrom(PropertiesOpen.class)) {
            return (T) getOpenSupport();
        } else if(clazz.isAssignableFrom(PropertiesEditorSupport.class)) {
            return (T) ((PropertiesFileEntry)getPrimaryEntry()).getPropertiesEditor();
        } else {
            return null;
        }
    }

    // Accessibility from PropertiesOpen:
    CookieSet getCookieSet0() {
        return getCookieSet();
    }
    
    @Override
    protected FileObject handleRename(String name) throws IOException {
        boolean baseNameChanged = false;
        BundleStructure oldStructure = (MultiBundleStructure) bundleStructure;
        FileObject fo = this.getPrimaryFile();
        PropertiesOpen openCookie = (PropertiesOpen) getCookie(OpenCookie.class);
        if (openCookie != null) {
            openCookie.removeModifiedListener(this);
            openCookie.close();
        }
        if (bundleStructure != null && bundleStructure.getEntryCount()>1) {
            if (!Util.getBaseName(name).equals(Util.getBaseName(this.getName()))) {
                //This means that new OpenCookie should be created
                baseNameChanged = true;
                bundleStructure = null;
                openSupport = null;
            }
        }
        try {
            return super.handleRename(name);
        } finally {
            if (baseNameChanged && oldStructure!=null && oldStructure.getEntryCount()>1) {
                oldStructure.updateEntries();
                oldStructure.notifyOneFileChanged(fo);
            }
            bundleStructure = null;
            openSupport = null;
        }
    }


    /** Copies primary and secondary files to new folder.
     * Overrides superclass method.
     * @param df the new folder
     * @return data object for the new primary
     * @throws IOException if there was a problem copying
     * @throws UserCancelException if the user cancelled the copy */
    @Override
    protected synchronized DataObject handleCopy(DataFolder df) throws IOException {
        if (LOG.isLoggable(FINER)) {
            LOG.finer("handleCopy("                                     //NOI18N
                    + FileUtil.getFileDisplayName(df.getPrimaryFile()) + ')');
        }
        try {
//            pasteSuffix = createPasteSuffix(df);

            return super.handleCopy(df);
        } finally {
            pasteSuffix = null;
            bundleStructure = null;
        }
    }

    @Override
    protected void handleDelete() throws IOException {
        if (LOG.isLoggable(FINER)) {
            LOG.finer("handleDelete()");
        }
        PropertiesOpen openCookie = (PropertiesOpen) getCookie(OpenCookie.class);
        if (openCookie != null) {
            openCookie.removeModifiedListener(this);
//            openCookie.close();
            bundleStructure = null;
            openSupport = null;
        }
        super.handleDelete();
    }


    /** Moves primary and secondary files to a new folder.
     * Overrides superclass method.
     * @param df the new folder
     * @return the moved primary file object
     * @throws IOException if there was a problem moving
     * @throws UserCancelException if the user cancelled the move */
    @Override
    protected FileObject handleMove(DataFolder df) throws IOException {
        if (LOG.isLoggable(FINER)) {
            LOG.finer("handleMove("                                     //NOI18N
                    + FileUtil.getFileDisplayName(df.getPrimaryFile()) + ')');
        }

        BundleStructure oldStructure = (MultiBundleStructure) bundleStructure;
        FileObject fo = this.getPrimaryFile();
        // a simple fix of issue #92195 (impossible to save a moved prop. file):
        SaveCookie saveCookie = getCookie(SaveCookie.class);
        if (saveCookie != null) {
            saveCookie.save();
        }
        PropertiesOpen openCookie = (PropertiesOpen) getCookie(OpenCookie.class);
        if (openCookie != null) {
            openCookie.removeModifiedListener(this);
            openCookie.close();
            bundleStructure = null;
            openSupport = null;
        }
//        getCookieSet().remove(openCookie);
        try {
//            pasteSuffix = createPasteSuffix(df);

            return super.handleMove(df);
        } finally {
            //Here data object has old path still but in invalid state
            if (oldStructure!=null && oldStructure.getEntryCount()>1) {
                oldStructure.updateEntries();
                oldStructure.notifyOneFileChanged(fo);
            }
            pasteSuffix = null;
            bundleStructure = null;
            openSupport = null;
        }
    }

    /** Gets suffix used by entries by copying/moving. */
    String getPasteSuffix() {
        return pasteSuffix;
    }

    /** Only accessible method, it is necessary to call MultiDataObject's method
     * from this package.
     */
    void removeSecondaryEntry2(Entry fe) {
        if (LOG.isLoggable(FINER)) {
            LOG.finer("removeSecondaryEntry2(Entry "                    //NOI18N
                    + FileUtil.getFileDisplayName(fe.getFile()) + ')');
        }
        removeSecondaryEntry (fe);
    }

    /** Creates new name for this instance when moving/copying to new folder destination. 
     * @param folder new folder destination. */
    private String createPasteSuffix(DataFolder folder) {
        String basicName = getPrimaryFile().getName();

        DataObject[] children = folder.getChildren();


        // Repeat until there is not such file name.
        for(int i = 0; ; i++) {
            String newName;

            if (i == 0) {
                newName = basicName;
            } else {
                newName = basicName + i;
            }
            boolean exist = false;

            for(int j = 0; j < children.length; j++) {
                if(children[j] instanceof PropertiesDataObject && newName.equals(children[j].getName())) {
                    exist = true;
                    break;
                }
            }

            if(!exist) {
                if (i == 0) {
                    return ""; // NOI18N
                } else {
                    return "" + i; // NOI18N
                }
            }
        }
    }

    /** Returns open support. It's used by all subentries as open support too. */
    public PropertiesOpen getOpenSupport() {
        if (openSupport == null) {
            openSupport = ((MultiBundleStructure)getBundleStructure()).getOpenSupport();
            if (this.isValid())
                openSupport.addDataObject(this);
        }
        return openSupport;
    }

    /** Updates modification status of this dataobject from its entries. */
    void updateModificationStatus() {
        LOG.finer("updateModificationStatus()");                        //NOI18N
        boolean modif = false;
        if (((PresentableFileEntry)getPrimaryEntry()).isModified())
            modif = true;
        else {
            for (Iterator it = secondaryEntries().iterator(); it.hasNext(); ) {
                if (((PresentableFileEntry)it.next()).isModified()) {
                    modif = true;
                    break;
                }
            }
        }

        super.setModified(modif);
    }

    /** Provides node that should represent this data object. When a node for representation
     * in a parent is requested by a call to getNode (parent) it is the exact copy of this node
     * with only parent changed. This implementation creates instance
     * DataNode.
     * 

* This method is called only once. * * @return the node representation for this data object * @see DataNode */ @Override protected Node createNodeDelegate () { return new PropertiesDataNode(this, getLookup()); } Children getChildren() { return new PropertiesChildren(); } //TODO XXX Now it is always false boolean isMultiLocale() { return secondaryEntries().size() > 0; } /** * Find existing BundleStructure instance. * @return BundleStructure from first DataObject with the same base name or null */ protected synchronized BundleStructure findBundleStructure() { PropertiesDataObject dataObject = null; BundleStructure structure; try { dataObject = Util.findPrimaryDataObject(this); } catch (DataObjectNotFoundException doe) { Exceptions.printStackTrace(doe); } if(this == dataObject) { structure = new MultiBundleStructure(this); return structure; } else { return dataObject.getBundleStructure(); } } /** Getter for bundleStructure property */ protected BundleStructure getBundleStructureOrNull () { return bundleStructure; } /** Returns a structural view of this data object */ public BundleStructure getBundleStructure() { if (bundleStructure==null) { try { bundleStructure = Util.findBundleStructure(this.getPrimaryFile(), this.getPrimaryFile().getParent(), Util.getBaseName(this.getName())); if (bundleStructure == null) bundleStructure = new MultiBundleStructure(this); bundleStructure.updateEntries(); } catch (DataObjectNotFoundException ex) { Exceptions.printStackTrace(ex); return null; } } return bundleStructure; } protected void setBundleStructure(BundleStructure structure) { if (bundleStructure != structure) { bundleStructure = structure; } } /** Comparator used for ordering secondary files, works over file names */ public static Comparator getSecondaryFilesComparator() { return new KeyComparator(); } /** */ void fireNameChange() { LOG.finer("fireNameChange()"); //NOI18N firePropertyChange(PROP_NAME, null, null); } /** Deserialization. */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); initialize(); } /** Children of this PropertiesDataObject. */ private class PropertiesChildren extends Children.Keys { /** Listens to changes on the dataobject */ private PropertyChangeListener propertyListener = null; private PropertyChangeListener weakPropListener = null; /** Constructor.*/ PropertiesChildren() { super(); } /** Sets all keys in the correct order */ protected void mySetKeys() { TreeSet newKeys = new TreeSet(new Comparator() { public int compare(String o1, String o2) { if (o1 == o2) { return 0; } if (o1 == null) { return -1; } if (o2 == null) { return 1; } return o1.compareTo(o2); } }); newKeys.add(getPrimaryEntry().getFile().getName()); for (Entry entry : secondaryEntries()) { newKeys.add(entry.getFile().getName()); } setKeys(newKeys); } /** Called to notify that the children has been asked for children * after and that they should set its keys. Overrides superclass method. */ @Override protected void addNotify () { mySetKeys(); // listener if(propertyListener == null) { propertyListener = new PropertyChangeListener () { public void propertyChange(PropertyChangeEvent evt) { if(PROP_FILES.equals(evt.getPropertyName())) { if (isMultiLocale()) { mySetKeys(); } else { // These children are only used for two or more locales. // If only default locale is left, disconnect the listener. // This children object is going to be removed, but // for some reason it causes problems setting new keys here. if (propertyListener != null) { PropertiesDataObject.this.removePropertyChangeListener(weakPropListener); propertyListener = null; } } } } }; weakPropListener = WeakListeners.propertyChange(propertyListener, PropertiesDataObject.this); PropertiesDataObject.this.addPropertyChangeListener(weakPropListener); } } /** Called to notify that the children has lost all of its references to * its nodes associated to keys and that the keys could be cleared without * affecting any nodes (because nobody listens to that nodes). * Overrides superclass method. */ @Override protected void removeNotify () { setKeys(new ArrayList()); } /** Creates nodes for specified key. Implements superclass abstract method. */ protected Node[] createNodes(String entryName) { if (entryName == null) { return null; } PropertiesFileEntry entry = (PropertiesFileEntry)getPrimaryEntry(); if(entryName.equals(entry.getFile().getName())) { return new Node[] {entry.getNodeDelegate()}; } for(Iterator it = secondaryEntries().iterator();it.hasNext();) { entry = (PropertiesFileEntry)it.next(); if (entryName.equals(entry.getFile().getName())) { return new Node[] {entry.getNodeDelegate()}; } } return null; } } // End of class PropertiesChildren. }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy