Package org.mevenide.idea.psi.util

Source Code of org.mevenide.idea.psi.util.PsiIndexedPropertyChangeListener

package org.mevenide.idea.psi.util;

import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiTreeChangeAdapter;
import com.intellij.psi.PsiTreeChangeEvent;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlText;
import com.intellij.util.IncorrectOperationException;
import java.util.HashSet;
import java.util.Set;
import javax.swing.event.EventListenerList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mevenide.idea.util.IDEUtils;
import org.mevenide.idea.util.event.*;

/**
* Manages events for table-like tag model, where a single tag path represents the "container" tag,
* under which there are multiple "row" tags.
*
* <p>PSI events are mapped for the specified row they changed and cause an {@link
* java.beans.IndexedPropertyChangeEvent} to be triggered.</p>
*
* @author Arik
*/
public class PsiIndexedPropertyChangeListener extends PsiTreeChangeAdapter
        implements BeanRowsObservable {
    /**
     * Logging.
     */
    private static final Log LOG = LogFactory.getLog(PsiIndexedPropertyChangeListener.class);

    /**
     * Synchronization lock.
     */
    private final Object LOCK = new Object();

    /**
     * Maps properties to XML tag paths.
     */
    private final XmlTagPropertyMapper mapper = new XmlTagPropertyMapper();

    /**
     * Property change listeners container.
     */
    private final EventListenerList listenerList = new EventListenerList();

    /**
     * The path to the tag containing all row tags.
     */
    private final XmlTagPath containerPath;

    /**
     * The name of the tags that represent individual rows.
     */
    private final String rowTagName;

    /**
     * The path to the tag containing all row tags.
     */
    private final XmlTagPath rowPath;

    /**
     * Is the current set of PSI events generated by the XML tag path, or by an outside source?
     */
    private boolean ignoreEvents = false;

    /**
     * @param pContainerPath
     * @param pRowTagName
     */
    public PsiIndexedPropertyChangeListener(final XmlTagPath pContainerPath,
                                            final String pRowTagName) {
        containerPath = pContainerPath;
        rowTagName = pRowTagName;
        rowPath = new XmlTagPath(containerPath, rowTagName);
    }

    /**
     * Returns the path to the container tag.
     *
     * @return tag path
     */
    public final XmlTagPath getContainerPath() {
        return containerPath;
    }

    /**
     * Returns the name of the row tags.
     *
     * @return string
     */
    public final String getRowTagName() {
        return rowTagName;
    }

    /**
     * Registers the given listener for events.
     *
     * @param pListener the listener to register
     */
    public void addBeanRowsListener(BeanRowsListener pListener) {
        synchronized (LOCK) {
            listenerList.add(BeanRowsListener.class, pListener);
        }
    }

    /**
     * Unregisters the given listener.
     *
     * @param pListener the listener to remove
     */
    public void removeBeanRowsListener(BeanRowsListener pListener) {
        synchronized (LOCK) {
            listenerList.remove(BeanRowsListener.class, pListener);
        }
    }

    /**
     * Registers the given property name with the specified tag path.
     *
     * @param pPropertyName the property name
     * @param pTagPath      the corresponding tag path
     */
    public final void registerTag(final String pPropertyName,
                                  final XmlFile pFile,
                                  final String pTagPath) {
        synchronized (LOCK) {
            final XmlTagPath tagPath = new XmlTagPath(rowPath, pTagPath);
            mapper.putTagPath(pPropertyName, tagPath);
        }
    }

    public final String[] getValues() {
        final XmlTag containerTag = containerPath.getTag();
        if (containerTag == null)
            return new String[0];

        final XmlTag[] rowTags = containerTag.findSubTags(rowTagName);
        final String[] values = new String[rowTags.length];
        for (int i = 0; i < rowTags.length; i++)
            values[i] = rowTags[i].getValue().getTrimmedText();

        return values;
    }

    public final String getValue(final int pRow) {
        return getValue(pRow, null);
    }

    public final String getValue(final int pRow, final String pPropertyName) {
        synchronized (LOCK) {
            rowPath.setRow(pRow);

            final XmlTagPath path;
            if (pPropertyName == null || pPropertyName.trim().length() == 0)
                path = rowPath;
            else
                path = mapper.getTagPath(pPropertyName);

            return path.getStringValue();
        }
    }

    public final void setValue(final int pRow,
                               final Object pValue) {
        setValue(pRow, null, pValue);
    }

    public final void setValue(final int pRow,
                               final String pPropertyName,
                               final Object pValue) {
        final String value = pValue == null ? null : pValue.toString();

        synchronized (LOCK) {
            ignoreEvents = true;
            try {
                rowPath.setRow(pRow);

                final XmlTagPath path;
                if (pPropertyName == null || pPropertyName.trim().length() == 0)
                    path = rowPath;
                else
                    path = mapper.getTagPath(pPropertyName);

                path.setValueProtected(value);
            }
            finally {
                ignoreEvents = false;
            }
        }
    }

    public final int getRowCount() {
        final XmlTag containerTag = containerPath.getTag();
        if (containerTag == null)
            return 0;

        final XmlTag[] rowTags = containerTag.findSubTags(rowTagName);
        return rowTags.length;
    }

    public final int appendRow() {
        final RowAppenderRunnable addTagCmd = new RowAppenderRunnable();

        IDEUtils.runCommand(containerPath.getProject(), addTagCmd);
        return addTagCmd.getResult();
    }

    public void deleteRows(final int... pRowIndices) {
        final Runnable deleteTagsCmd = new Runnable() {
            public void run() {
                try {
                    final XmlTag containerTag = containerPath.getTag();
                    if (containerTag == null)
                        return;

                    final XmlTag[] childTags = containerTag.findSubTags(rowTagName);
                    final Set<XmlTag> tags = new HashSet<XmlTag>(pRowIndices.length);
                    for (int i = 0; i < pRowIndices.length; i++)
                        tags.add(childTags[pRowIndices[i]]);

                    for (XmlTag xmlTag : tags)
                        xmlTag.delete();
                }
                catch (IncorrectOperationException e) {
                    LOG.error(e.getMessage(), e);
                }
            }
        };

        IDEUtils.runCommand(containerPath.getProject(), deleteTagsCmd);
    }

    /**
     * Returns the row index that this element belongs to.
     *
     * <p>If the given PSI element is not a child of any row, this method will return {@code
     * null}.</p>
     *
     * @param pElement the PSI element to test
     *
     * @return the row tag
     */
    private Integer findRowForElement(final PsiElement pElement) {
        synchronized (LOCK) {
            final XmlTag containerTag = containerPath.getTag();
            if (containerTag == null)
                return null;

            final XmlTag[] rowTags = containerTag.findSubTags(rowTagName);
            for (int i = 0; i < rowTags.length; i++) {
                rowPath.setRow(i);
                final XmlTag rowTag = rowPath.getTag();
                if (PsiTreeUtil.isAncestor(rowTag, pElement, false))
                    return i;
            }

            return null;
        }
    }

    private void handleChildAddition(final PsiElement pChild) {
        final XmlTag containerTag = containerPath.getTag();
        if (containerTag == null)
            return;

        final XmlTag[] rowTags = containerTag.findSubTags(rowTagName);
        for (int i = 0; i < rowTags.length; i++) {
            rowPath.setRow(i);
            final XmlTag rowTag = rowPath.getTag();
            if (PsiTreeUtil.isAncestor(rowTag, pChild, false)) {
                if (pChild.equals(rowTag))
                    fireRowAddedEvent(i);
                else {
                    final String property = mapper.findPropertyForElement(pChild);
                    if (property != null)
                        fireRowChangedEvent(i, property);
                }
                break;
            }
        }
    }

    private void handleChildRemoval(final PsiElement pParent, final PsiElement pChild) {
        final XmlTag parent = PsiTreeUtil.getParentOfType(pParent,
                                                          XmlTag.class,
                                                          false);
        if (parent == null)
            return;

        //
        //if the removed tag was the container tag, fire global change event
        //
        final XmlTag containerTag = containerPath.getTag();
        if (containerTag == null) {
            if (pChild instanceof XmlTag) {
                final XmlTag tag = (XmlTag) pChild;
                final String[] path = PsiUtils.getPathAndConcat(parent, tag.getName());
                if (path.equals(containerPath.getPathTokens()))
                    fireRowsChangedEvent();
            }
            return;
        }

        //
        //if the removed child was a text node, simply find which property it belonged to
        //
        if (pChild instanceof XmlText) {
            final XmlTag[] rowTags = containerTag.findSubTags(rowTagName);
            for (int i = 0; i < rowTags.length; i++) {
                rowPath.setRow(i);
                if (PsiTreeUtil.isAncestor(rowPath.getTag(), pChild, false)) {
                    final String property = mapper.findPropertyForElement(pChild);
                    if (property != null)
                        fireRowChangedEvent(i, property);
                }
            }
        }

        //
        //if the removed child is a tag, check if it is a row tag or a property tag
        //
        else if (pChild instanceof XmlTag) {
            rowPath.setRow(null);
            final XmlTag tag = (XmlTag) pChild;
            final String[] path = PsiUtils.getPathAndConcat(parent, tag.getName());

            //if the removed tag is a registered property tag
            final String propertyName = mapper.findPropertyByPath(path);
            if (propertyName != null) {
                final int row = findRowForElement(parent);
                fireRowChangedEvent(row, propertyName);
            }

            //if the removed tag is a row tag
            else if (rowTagName.equals(tag.getName())) {
                //TODO: inspect event's offset to determine row number
                //fireRowRemovedEvent(rowByOffset);

                fireRowsChangedEvent();
            }
        }
    }

    @Override
    public final void childAdded(final PsiTreeChangeEvent pEvent) {
        synchronized (LOCK) {
            if (ignoreEvents)
                return;

            handleChildAddition(pEvent.getChild());
        }
    }

    @Override
    public final void childRemoved(final PsiTreeChangeEvent pEvent) {
        synchronized (LOCK) {
            if (ignoreEvents)
                return;

            handleChildRemoval(pEvent.getParent(), pEvent.getChild());
        }
    }

    @Override
    public final void childReplaced(final PsiTreeChangeEvent pEvent) {
        synchronized (LOCK) {
            if (ignoreEvents)
                return;

            handleChildRemoval(pEvent.getParent(), pEvent.getOldChild());
            handleChildAddition(pEvent.getNewChild());
        }
    }

    private BeanRowsListener[] getListeners() {
        return listenerList.getListeners(BeanRowsListener.class);
    }

    private void fireRowAddedEvent(final int pRow) {
        BeanRowEvent event = null;
        for (BeanRowsListener listener : getListeners()) {
            if (event == null)
                event = new BeanRowAddedEvent(this, pRow);
            listener.rowAdded(event);
        }
    }

    private void fireRowRemovedEvent(final int pRow) {
        BeanRowEvent event = null;
        for (BeanRowsListener listener : getListeners()) {
            if (event == null)
                event = new BeanRowRemovedEvent(this, pRow);
            listener.rowRemoved(event);
        }
    }

    private void fireRowChangedEvent(final int pRow,
                                     final String pProperty) {
        final XmlTagPath path = mapper.getTagPath(pProperty);
        final String value = path.getStringValue();

        BeanRowEvent event = null;
        for (BeanRowsListener listener : getListeners()) {
            if (event == null)
                event = new BeanRowChangedEvent(this, pRow, pProperty, value);
            listener.rowChanged(event);
        }
    }

    private void fireRowsChangedEvent() {
        BeanRowEvent event = null;
        for (BeanRowsListener listener : getListeners()) {
            if (event == null)
                event = new BeanRowsChangedEvent(this);
            listener.rowsChanged(event);
        }
    }

    private class RowAppenderRunnable implements Runnable {
        private int result = -1;

        public void run() {
            result = -1;
            try {
                final XmlTag containerTag = containerPath.ensureTag();
                final String namespace = containerTag.getNamespace();
                final XmlTag rowTag = (XmlTag) containerTag.add(
                        containerTag.createChildTag(rowTagName,
                                                    namespace,
                                                    null,
                                                    false));

                final XmlTag[] childTags = containerTag.findSubTags(rowTagName);
                for (int i = 0; i < childTags.length; i++) {
                    XmlTag tag = childTags[i];
                    if (tag.equals(rowTag)) {
                        result = i;
                        break;
                    }
                }
            }
            catch (IncorrectOperationException e) {
                LOG.error(e.getMessage(), e);
            }
        }

        public final int getResult() {
            return result;
        }
    }
}
TOP

Related Classes of org.mevenide.idea.psi.util.PsiIndexedPropertyChangeListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.