/*******************************************************************************
* Copyright (c) 2006 IONA Technologies PLC
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* 
* Contributors:
*     IONA Technologies PLC - initial API and implementation
*******************************************************************************/
package org.eclipse.stp.sc.common.views;

import java.util.Map;
import java.util.List;
import java.util.HashMap;
import java.util.Iterator;
import java.lang.reflect.Method;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TreeEditor;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.INullSelectionListener;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.stp.common.logging.LoggingProxy;
import org.eclipse.stp.sc.common.CommonResources;
import org.eclipse.stp.sc.common.annotations.ScJDTAnnUtils;
import org.eclipse.stp.sc.common.annotations.ScAnnotationSupportUtils;
import org.eclipse.stp.sc.common.utils.JDTUtils;
import org.eclipse.stp.sc.common.views.AnnotationViewerContentProvider;
import org.eclipse.stp.sc.common.views.AnnotationViewerLabelProvider;

/**
 * this view is to display and edit the JAXWS 2.0 annotation setup for a selected java element
 */
public class AnnotationView extends ViewPart {

	public static final String ANNOTATION_VIEW_ID = "org.eclipse.stp.sc.common.views.AnnotationView";
    private static final LoggingProxy LOG = LoggingProxy.getlogger(AnnotationView.class);

    // edit support -- start
    protected TreeEditor treeEditor;
    protected Combo combo = null;
    protected Text text = null;

    Image iconOff = CommonResources.getImageDescriptor("obj16/annotation_false_obj.gif").createImage();
    Image iconOn = CommonResources.getImageDescriptor("obj16/annotation_true_obj.gif").createImage();
    
    Map<String, Annotation> annotationNodesMap;

    public TreeViewer annotationsviewer;
    // edit support -- end

    // the listener to register with the selection service 
    public ISelectionListener workbenchSelectionListener = new AnnotationSelectionListener();
    protected IPartListener2 workbenchPartListener = new JavaEditorListener();
    
    private IMember selectedJdtMember;
    private SingleVariableDeclaration selectedJdtMethodParam;
    private boolean editSupportEnabled;
    
    private CompilationUnit compilationUnitAstNode;
    private ICompilationUnit compilationUnitMember;

    private IFile javaEditorInputFile;

    static final String JAVA_EDITOR_ID = "org.eclipse.jdt.ui.CompilationUnitEditor";

    private int offset;
    
    private String originalValue;

    public void createPartControl(Composite parent) {
        annotationsviewer = new TreeViewer(parent, SWT.MULTI | SWT.FULL_SELECTION);
        
        annotationsviewer.getTree().setHeaderVisible(true);
        TreeColumn column = new TreeColumn(annotationsviewer.getTree(), SWT.LEFT);
        column.setText(CommonResources.getString("annotation.view.Col1.lbl"));
        column.setResizable(true);
        column.setWidth(300);
        column = new TreeColumn(annotationsviewer.getTree(), SWT.LEFT);
        column.setText(CommonResources.getString("annotation.view.Col2.lbl"));
        column.setResizable(true);
        column.setWidth(700);

        annotationsviewer.setLabelProvider(new AnnotationViewerLabelProvider(this));
        annotationsviewer.setContentProvider(new AnnotationViewerContentProvider(this));
        annotationsviewer.setSorter(new ViewerSorter());

        // edit support -- start
        treeEditor = new TreeEditor(annotationsviewer.getTree());
        treeEditor.grabHorizontal = true;
        treeEditor.grabVertical = true;
        annotationsviewer.getTree().addSelectionListener(new SelectionAdapter() {
        	public void widgetSelected(SelectionEvent event) {
        		activateEditSupport(event);
        	}
        });
        // edit support -- end

        IWorkbenchWindow window = getSite().getWorkbenchWindow();
        window.getSelectionService().addPostSelectionListener(workbenchSelectionListener);
        window.getPartService().addPartListener(workbenchPartListener);
        
        if (window.getActivePage() != null) {
        	IEditorPart editor = window.getActivePage().getActiveEditor();
        	if (editor != null) {
	        	ISelection selection = window.getActivePage().getSelection();
	        	workbenchSelectionListener.selectionChanged(editor, selection);
        	}
        }
    }

    public void setFocus() {
    	this.annotationsviewer.getControl().setFocus();
    }

    public void dispose() {
        iconOff.dispose();
        iconOn.dispose();
        
        // important: We need do unregister our listener when the view is disposed
        IWorkbenchWindow window = getSite().getWorkbenchWindow();
        window.getSelectionService().removePostSelectionListener(workbenchSelectionListener);
        window.getPartService().removePartListener(workbenchPartListener);
        super.dispose();
    }

    // edit support -- start
    /**
     * activate the edit support for the new tree selection and its corresponding annotation or attribute
     */
    private void activateEditSupport(SelectionEvent event) {
        
        if (!editSupportEnabled) {
            LOG.debug("edit support disabld");
            return;
        }
        
        disposeEditWidgets();

        TreeItem item = null;
        Tree tree = (Tree)event.getSource();

        if (tree.getSelectionCount() == 1) {
            item = tree.getSelection()[0];
        } else {
            return;
        }

        Object element = item.getData();
        
        activateEditSupport(element, item);
    }

    private void activateEditSupport(Object element, TreeItem item) {
        // case 1: adding/removing annotation to/from the java element
        if (element instanceof Class) {
            
            handleEditOnAnnotation(item);

        } else if (element instanceof Method) {
            // no point to let the user edit the properties of annotation that aren't even used
            if (("" + false).equals(item.getParentItem().getText(1))) {
                disposeEditWidgets();
                return;
            }

            Class returnTypeClass = ((Method)element).getReturnType();

            // case 2: support for enum based properties
            if (returnTypeClass.isEnum()) {
                handleEditOnEnumAttribute(item, returnTypeClass);
            } else if (returnTypeClass.isArray()) {
            	handleEditOnArrayAttribute(item, returnTypeClass);
            } else if (returnTypeClass.equals(String.class)) {
                handleEditOnStringAttribute(item);
            } else if (returnTypeClass.equals(Boolean.TYPE) || returnTypeClass.equals(Boolean.class)) {
                handleEditOnBooleanAttribute(item);
            } else if (returnTypeClass.equals(Integer.TYPE) || returnTypeClass.equals(Integer.class)
                       || returnTypeClass.equals(Long.TYPE) || returnTypeClass.equals(Long.class)
                       || returnTypeClass.equals(Float.TYPE) || returnTypeClass.equals(Float.class)
                       || returnTypeClass.equals(Double.TYPE) || returnTypeClass.equals(Double.class)) {
                handleEditOnNumericAttribute(item);
            } else {
                handleEditOnStringAttribute(item);
            }
        } else if (element instanceof Object[]) {
        	Object[] pair = (Object[])element;
        	if (pair[0] instanceof String) {
        		if (item.getParentItem().getParentItem().getText(1).equals("true")) {
        			handleEditOnArrayElement(item);
        		}
        	} else if (pair[0] instanceof Method) {
        		Method method = (Method)pair[0];
        		activateEditSupport(method, item);
        	}
        }
    }

    @SuppressWarnings("unchecked")
    private void handleEditOnArrayElement(TreeItem item) {
        
        if (!editSupportEnabled) {
            LOG.debug("edit support disabld");
            return;
        }

        //Set the editor for the second column in the row                 
        combo = new Combo(annotationsviewer.getTree(), SWT.READ_ONLY);
        combo.setItems(new String[] {"" + true, "" + false });
        combo.setFocus();

        treeEditor.minimumHeight = combo.getSize().y;
        // TODO see if we can make the control's width adaptable to the size of the content
        //treeEditor.minimumWidth = annotationsviewer.getTree().getColumn(1).getWidth();
        treeEditor.setEditor(combo, item, 1);

        combo.select(combo.indexOf(treeEditor.getItem().getText(1)));

        combo.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent event) {
                    try {
                        
                        String originalValue = treeEditor.getItem().getText(1);
                        String updatedValueStr = ((Combo)event.getSource()).getText();
                        disposeEditWidgets();

                        if (!originalValue.equals(updatedValueStr)) {
                            Boolean value = new Boolean(updatedValueStr);
                            
                            treeEditor.getItem().setText(1, updatedValueStr);
                            treeEditor.getItem().setImage(value ? iconOn : iconOff);

                            Class topAnnoClass = (Class)treeEditor.getItem().getParentItem().getParentItem().getData();
                        	NormalAnnotation topAnnoNode = (NormalAnnotation)annotationNodesMap.get(topAnnoClass.getSimpleName());

                        	MemberValuePair mvp = null;
                        	Method declaringMethod = (Method)treeEditor.getItem().getParentItem().getData();
                        	for (int i = 0; i < topAnnoNode.values().size(); i++) {
                        		MemberValuePair pair = (MemberValuePair)topAnnoNode.values().get(i);
                        		if (pair.getName().getIdentifier().equals(declaringMethod.getName())) {
                        			mvp = pair;
                        			break;
                        		}
                        	}
                        	
                            List expList = ((ArrayInitializer)mvp.getValue()).expressions();

                            if (value) {
                            	try {
                            		Object[] data = (Object[])treeEditor.getItem().getData();
                            		String className = (String)data[0];
                            		className = className.substring(0, className.indexOf(":"));
                            		Class itemClass = Class.forName(className);
                            		NormalAnnotation anno = (NormalAnnotation)ScAnnotationSupportUtils.getDefaultedAnnotationNode(
                            				itemClass, compilationUnitAstNode, selectedJdtMember, selectedJdtMethodParam);
                            		expList.add(anno);
                            		data[1] = anno;
                            	}
                            	catch (ClassNotFoundException ignored) {}
                            }
                            else {
                            	Object[] data = (Object[])treeEditor.getItem().getData();
                            	String extraInfo = (String)data[0];
                            	int pos = extraInfo.indexOf(":Existing");
                            	String posStr = extraInfo.substring(pos + ":Existing".length());
                            	expList.remove(Integer.valueOf(posStr).intValue());
                            	data[1] = null;
                            }
    
                            ScJDTAnnUtils.addAnnotationToCu(compilationUnitMember,
                                                            compilationUnitAstNode,
                                                            topAnnoNode,
                                                            selectedJdtMember,
                                                            selectedJdtMethodParam);
                            refreshViewContent();
                        }
                    } catch (JavaModelException e) {
                        LOG.error("Java model update failed: ", e);
                    } catch (MalformedTreeException e) {
                        LOG.error("Java model update failed: ", e);
                    } catch (BadLocationException e) {
                        LOG.error("Java model update failed: ", e);
                    } finally {
                        disposeEditWidgets();
                    }
                }
            });
    }
    
    /**
     * prepares and enables the widgets necessary to handle the addition or removal of the annotaion 
     * represented by the <code>TreeItem</code> param
     * @param item
     */
    private void handleEditOnAnnotation(TreeItem item) {
        
        if (!editSupportEnabled) {
            LOG.debug("edit support disabld");
            return;
        }

        //Set the editor for the second column in the row                 
        combo = new Combo(annotationsviewer.getTree(), SWT.READ_ONLY);
        combo.setItems(new String[] {"" + true, "" + false });
        combo.setFocus();

        treeEditor.minimumHeight = combo.getSize().y;
        // TODO see if we can make the control's width adaptable to the size of the content
        //treeEditor.minimumWidth = annotationsviewer.getTree().getColumn(1).getWidth();
        treeEditor.setEditor(combo, item, 1);

        combo.select(combo.indexOf(treeEditor.getItem().getText(1)));

        combo.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent event) {
                    try {
                        
                        String originalValue = treeEditor.getItem().getText(1);
                        String updatedValueStr = ((Combo)event.getSource()).getText();
                        disposeEditWidgets();

                        if (!originalValue.equals(updatedValueStr)) {
                            Boolean value = new Boolean(updatedValueStr);
                            
                            treeEditor.getItem().setText(1, updatedValueStr);
                            treeEditor.getItem().setImage(value ? iconOn : iconOff);
                               
                            Class<? extends java.lang.annotation.Annotation> annotationClass = 
                                (Class<? extends java.lang.annotation.Annotation>)treeEditor.getItem().getData();
                            String annotationName = annotationClass.getSimpleName();
                            
                            if (value) {
                                Annotation annotationNode = 
                                    ScAnnotationSupportUtils.getDefaultedAnnotationNode(annotationClass,
                                                                                    compilationUnitAstNode,
                                                                                    selectedJdtMember,
                                                                                    selectedJdtMethodParam);
                                ScJDTAnnUtils.addAnnotationToCu(compilationUnitMember,
                                                           compilationUnitAstNode,
                                                           annotationNode,
                                                           selectedJdtMember,
                                                           selectedJdtMethodParam);
                                
                            } else {
                                Annotation annotationNode = (Annotation)annotationNodesMap.get(annotationName);
                                JDTUtils.removeAnnotationFromCu(compilationUnitMember,
                                                                compilationUnitAstNode,
                                                                annotationNode,
                                                                selectedJdtMember,
                                                                selectedJdtMethodParam);
                            }
    
                            refreshViewContent();
                        }
                    } catch (JavaModelException e) {
                        LOG.error("Java model update failed: ", e);
                    } catch (MalformedTreeException e) {
                        LOG.error("Java model update failed: ", e);
                    } catch (BadLocationException e) {
                        LOG.error("Java model update failed: ", e);
                    } finally {
                        disposeEditWidgets();
                    }
                }
            });
    }

    /**
     * prepares and enables the widgets necessary to handle the update of the annotaion attribute 
     * represented by the <code>TreeItem</code> param. the widget produced will be specifically to handle a 
     * text based attribute
     * @param item
     */
    private void handleEditOnStringAttribute(TreeItem item) {

        if (!editSupportEnabled) {
            return;
        }

        text = new Text(annotationsviewer.getTree(), SWT.BORDER);
        //text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
        text.setForeground(item.getForeground());
        text.setEditable(true);
        text.selectAll();
        text.setFocus();

        // Recalculate the minimum width for the editor
        treeEditor.minimumWidth = text.getBounds().width;

        // Set the control into the editor
        treeEditor.setEditor(text, item, 1);
        text.setText(treeEditor.getItem().getText(1));

        text.addKeyListener(new KeyAdapter() {
            public void keyReleased(KeyEvent e) {
                if ((text != null) 
                        && !text.isDisposed()
                        && ((e.keyCode == SWT.CR) || (e.keyCode == SWT.KEYPAD_CR))) {
                    processUpdateOnTextAttribute();
                }
            }
        });

        text.addFocusListener(new FocusAdapter() {
            public void focusLost(FocusEvent event) {
                processUpdateOnTextAttribute();
            }
        });
    }

    /**
     * prepares and enables the widgets necessary to handle the update of the annotaion attribute 
     * represented by the <code>TreeItem</code> param. the widget produced will be specifically to handle a 
     * numerical attribute
     * @param item
     */
    private void handleEditOnNumericAttribute(TreeItem item) {

        if (!editSupportEnabled) {
            return;
        }

        text = new Text(annotationsviewer.getTree(), SWT.BORDER);
        //text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
        text.setForeground(item.getForeground());
        text.setEditable(true);
        text.selectAll();
        text.setFocus();

        // Recalculate the minimum width for the editor
        treeEditor.minimumWidth = text.getBounds().width;

        // Set the control into the editor
        treeEditor.setEditor(text, item, 1);
        text.setText(treeEditor.getItem().getText(1));

        text.addKeyListener(new KeyAdapter() {
            public void keyReleased(KeyEvent e) {
                if ((e.keyCode == SWT.CR) || (e.keyCode == SWT.KEYPAD_CR)) {
                    processUpdateOnNumericAttribute();
                }
            }
        });

        text.addFocusListener(new FocusAdapter() {
            public void focusLost(FocusEvent event) {
                processUpdateOnNumericAttribute();
            }
        });
    }

    /**
     * prepares and enables the widgets necessary to handle the update of the annotaion attribute 
     * represented by the <code>TreeItem</code> param. the widget produced will be specifically to handle an 
     * array based attribute
     * @param item
     */
    private void handleEditOnArrayAttribute(TreeItem item, Class returnTypeClass) {

    	if (!editSupportEnabled) {
            return;
        }
    	
        text = new Text(annotationsviewer.getTree(), SWT.BORDER);
        //text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
        text.setForeground(item.getForeground());
        text.setEditable(false);
        text.selectAll();
        text.setFocus();

        // Recalculate the minimum width for the editor
        treeEditor.minimumWidth = text.getBounds().width;

        // Set the control into the editor
        treeEditor.setEditor(text, item, 1);
        text.setText(treeEditor.getItem().getText(1));

        text.addKeyListener(new KeyAdapter() {
            public void keyReleased(KeyEvent e) {
                if ((text != null) 
                        && !text.isDisposed()
                        && ((e.keyCode == SWT.CR) || (e.keyCode == SWT.KEYPAD_CR))) {
                    processUpdateOnTextAttribute();
                }
            }
        });

        text.addFocusListener(new FocusAdapter() {
            public void focusLost(FocusEvent event) {
                processUpdateOnTextAttribute();
            }
        });
    }

    /**
     * prepares and enables the widgets necessary to handle the update of the annotaion attribute 
     * represented by the <code>TreeItem</code> param. the widget produced will be specifically to handle an 
     * enum based attribute
     * @param item
     */
    private void handleEditOnEnumAttribute(TreeItem item, Class returnTypeClass) {
        
        if (!editSupportEnabled) {
            return;
        }

        final Object[] enumsConstants = returnTypeClass.getEnumConstants();

        // Set the editor for the second column in the row                 
        combo = new Combo(annotationsviewer.getTree(), SWT.READ_ONLY);
        combo.setBackground(item.getBackground());
        combo.setForeground(item.getForeground());
        combo.setFocus();

        String[] comboValues = new String[enumsConstants.length];

        for (int i = 0; i < enumsConstants.length; i++) {
            comboValues[i] = enumsConstants[i].toString();
        }

        combo.setItems(comboValues);
        treeEditor.minimumHeight = combo.getSize().y;
        // TODO see if we can make the control's width adaptable to the size of the content
        //treeEditor.minimumWidth = annotationsviewer.getTree().getColumn(1).getWidth();
        treeEditor.setEditor(combo, item, 1);

        combo.select(combo.indexOf(treeEditor.getItem().getText(1)));

        combo.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent event) {

                    String originalValue = treeEditor.getItem().getText(1);
                    String updatedValueStr = ((Combo)event.getSource()).getText();
                    disposeEditWidgets();

                    if (!originalValue.equals(updatedValueStr)) {
                        treeEditor.getItem().setText(1, updatedValueStr);
                        for (int i = 0; i < enumsConstants.length; i++) {
                            if (enumsConstants[i].toString().endsWith(updatedValueStr)) {
                                applyAnnotationAttributeUpdate(enumsConstants[i]);
                                break;
                            }
                        }
                    }
                }
            });
    }

    private void handleEditOnBooleanAttribute(TreeItem item) {

        if (!editSupportEnabled) {
            return;
        }

        // Set the editor for the second column in the row                 
        combo = new Combo(annotationsviewer.getTree(), SWT.READ_ONLY);
        combo.setBackground(item.getBackground());
        combo.setForeground(item.getForeground());
        combo.setFocus();

        combo.setItems(new String[]{(new Boolean(true)).toString().toLowerCase(),
                                    (new Boolean(false)).toString().toLowerCase()});

        treeEditor.minimumHeight = combo.getSize().y;
        // TODO see if we can make the control's width adaptable to the size of the content
        //treeEditor.minimumWidth = annotationsviewer.getTree().getColumn(1).getWidth();
        treeEditor.setEditor(combo, item, 1);

        combo.select(combo.indexOf(treeEditor.getItem().getText(1).toLowerCase()));

        combo.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent event) {
                    String originalValue = treeEditor.getItem().getText(1);
                    String updatedValueStr = ((Combo)event.getSource()).getText();
                    disposeEditWidgets();

                    if (!originalValue.equals(updatedValueStr)) {
                        treeEditor.getItem().setText(1, updatedValueStr);
                        applyAnnotationAttributeUpdate(new Boolean(updatedValueStr));
                    }
                }
            });
    }

    private void disposeEditWidgets() {
        if ((text != null) && !text.isDisposed()) {
            text.dispose();
            text = null;
        }

        if ((combo != null) && !combo.isDisposed()) {
            combo.dispose();
            combo = null;
        }
    }

    // edit support -- end

    /**
     * initiate the update process of the view content.
     */
    private void updateViewContent(IMember selectedItem,
                                   int anOffset,
                                   boolean enableEditSupport) {
        
        if (selectedItem == null) {
            return;
        }

        LOG.debug("view updated, edit enabled : " + enableEditSupport);    
        editSupportEnabled = enableEditSupport;
        selectedJdtMember = selectedItem;
        compilationUnitAstNode =  JDTUtils.getDomRootCompilationUnit(selectedJdtMember);
        compilationUnitMember = selectedJdtMember.getCompilationUnit();
        offset = anOffset;
        
        if (selectedItem instanceof IMethod && offset > 0) {
            selectedJdtMethodParam = JDTUtils.getMethodParamDeclaration(compilationUnitAstNode,
                                                                        (IMethod) selectedJdtMember,
                                                                        offset);
        } else { 
            selectedJdtMethodParam = null;
        }
        
        updateViewDescription();

        List<Annotation> annotationNodes = selectedJdtMethodParam != null 
                                            ? JDTUtils.getAnnotationsFromParamDecl(selectedJdtMethodParam) 
                                                : JDTUtils.getAnnotations(compilationUnitAstNode,
                                                                          selectedJdtMember);
        annotationNodesMap = new HashMap<String, Annotation>(annotationNodes.size());
        for (Iterator iter = annotationNodes.iterator(); iter.hasNext();) {
            Annotation currentAnnotationNode = (Annotation)iter.next();
            annotationNodesMap.put(currentAnnotationNode.getTypeName().getFullyQualifiedName(),
                                   currentAnnotationNode);
        }

        updateTreeContent();
        disposeEditWidgets();
    }

    private void refreshViewContent() {
        updateViewContent(selectedJdtMember, offset, editSupportEnabled);
    }
    
    private void updateViewDescription() {
        
        if (selectedJdtMember == null) {
            setContentDescription(CommonResources.getString("annotation.view.clear.lbl"));
            return;
        }

        int javaElementTypeId = selectedJdtMember.getElementType();
        String javaElementTypeLabel = (javaElementTypeId == IJavaElement.TYPE)
            ? CommonResources.getString("annotation.view.java.element.type.lbl")
            : ((javaElementTypeId == IJavaElement.METHOD && selectedJdtMethodParam == null)
                ? CommonResources.getString("annotation.view.java.element.method.lbl")
                : ((javaElementTypeId == IJavaElement.METHOD && selectedJdtMethodParam != null)
                    ? CommonResources.getString("annotation.view.java.element.param.lbl") 
                    : ((javaElementTypeId == IJavaElement.FIELD)
                            ? CommonResources.getString("annotation.view.java.element.field.lbl") 
                                    : "")));

        StringBuffer desc = new StringBuffer(javaElementTypeLabel)
                            .append(" : ")
                            .append(selectedJdtMember.getElementName())
                            .append(((selectedJdtMethodParam != null) 
                                        ? (" - " + selectedJdtMethodParam.getName().getIdentifier()) 
                                        : ""))
                            .append(" --- ")
                            .append(selectedJdtMember.getCompilationUnit().getPath().lastSegment());
        
        if (!editSupportEnabled) {
            desc.append(" --- ").append(CommonResources.getString("annotation.view.view.mode.lbl"));
        }
        setContentDescription(desc.toString());
    }

    private void clearView() {
        annotationsviewer.setInput(null);
        annotationsviewer.refresh();
        setContentDescription(CommonResources.getString("annotation.view.clear.lbl"));
    }

    private void updateTreeContent() {
        Object[] expandedElements = annotationsviewer.getExpandedElements();
        Object relevantInput = selectedJdtMethodParam != null ? selectedJdtMethodParam : selectedJdtMember;
        
        annotationsviewer.setInput(relevantInput);
        annotationsviewer.refresh();
        
        annotationsviewer.setExpandedElements(expandedElements);
    }

    @SuppressWarnings("unchecked")
    private void addOrUpdateAttributeInAnnotation(String attributeName,
                                                  Object value,
                                                  NormalAnnotation normalAnnotationNode) {

        List attributes = normalAnnotationNode.values();

        for (Iterator<?> iter = attributes.iterator(); iter.hasNext();) {
            Object next = iter.next();
            if (next instanceof MemberValuePair) {
                MemberValuePair property = (MemberValuePair)next;
                if (property.getName().getIdentifier().equals(attributeName)) {
                    normalAnnotationNode.values().remove(property);
                    break;
                }
            }
        }
       
        if (value != null) {
            MemberValuePair valPair = JDTUtils.newMemberValuePair(compilationUnitAstNode,
                                                                  attributeName,
                                                                  value);
            attributes.add(valPair);
        }
    }

    @SuppressWarnings("unchecked")
    private void applyAnnotationAttributeUpdate(Object formatedValue) {
        try {
            String attributeName = treeEditor.getItem().getText();
            
            if (treeEditor.getItem().getData() instanceof Method) {
            	Class annotationClass = (Class)treeEditor.getItem().getParentItem().getData();
            	Annotation annotationNode = annotationNodesMap.get(annotationClass.getSimpleName());
            	if (annotationNode == null) {
            		annotationNode = annotationNodesMap.get(annotationClass.getName());
            	}

            	if (annotationNode.isMarkerAnnotation()) {
            		// this annotation doesn't accept attibutes (no "()" in the source)
            		// TODO should convert to a normal annotation?
            		return; 
            	}
            	NormalAnnotation normalAnnotationNode = (NormalAnnotation)annotationNode;

            	Class attributeType = annotationClass.getMethod(attributeName, (Class[])null).getReturnType(); 
            	if (attributeType.equals(java.lang.Class.class)) {
            		String attributeValue = (String)formatedValue;
            		if (attributeValue.endsWith(".class")) {
            			attributeValue = attributeValue.substring(0, attributeValue.length() - ".class".length());
            		}
            		try {
            			formatedValue = Class.forName(attributeValue);
            			// JDTUtils.addImport(compilationUnitAstNode, attributeValue, ASTRewrite.create(compilationUnitAstNode.getAST()));
            		} catch (ClassNotFoundException e) {
            			try {
            				LOG.error("error to find class: " + attributeValue);
            				String originalClass = originalValue.substring(0, originalValue.length() - ".class".length());
            				formatedValue = Class.forName(originalClass);
            			} catch (ClassNotFoundException ignored) {}
            		}
            	} else if (attributeType.isArray()) {
            		// Array attribute cannot be edited directly
            	}
            
            	addOrUpdateAttributeInAnnotation(attributeName, formatedValue, normalAnnotationNode);
            
            	ScJDTAnnUtils.addAnnotationToCu(compilationUnitMember,
                                                compilationUnitAstNode,
                                                normalAnnotationNode,
                                                selectedJdtMember,
                                                selectedJdtMethodParam);
            	refreshViewContent();

            } else if (treeEditor.getItem().getData() instanceof Object[]) {
            	Object[] pair = (Object[])treeEditor.getItem().getData();
            	Class annotationClass = null;
            	if (pair[0] instanceof Method) {
            		MemberValuePair mvp = (MemberValuePair)pair[1];
            		if (mvp != null) {
            			// if (mvp.getValue() instanceof TypeLiteral) {
            			if (((Method)pair[0]).getReturnType().equals(Class.class)) {
            				/*
            				try {
            					String className = (String)formatedValue;
            					if (className.endsWith(".class")) {
            						className = className.substring(0, className.length() - ".class".length());
            					}
            					formatedValue = Class.forName(className);
            				}
            				catch (ClassNotFoundException ignored) {}
            				*/
        					String className = (String)formatedValue;
        					if (className.endsWith(".class")) {
        						className = className.substring(0, className.length() - ".class".length());
        					}
            	            AST ast = compilationUnitAstNode.getAST();
            	            int previousIndex = -1;
            	            int nextIndex = className.indexOf('.');
            	            Type theType;
            	            if (nextIndex == -1) {
            	            	theType = ast.newSimpleType(ast.newSimpleName(className));
            	            } else {
            	            	theType = ast.newSimpleType(ast.newSimpleName(className.substring(1 + previousIndex, nextIndex)));
            	            }
            	            previousIndex = nextIndex;
            	            nextIndex = className.indexOf('.', previousIndex + 1);
            	            while (nextIndex != -1) {
            	                String substring = className.substring(1 + previousIndex, nextIndex);
            	                theType = ast.newQualifiedType(theType, ast.newSimpleName(substring));
            	                previousIndex = nextIndex;
            	                nextIndex = className.indexOf('.', previousIndex + 1);
            	            }
            	            if (previousIndex != -1) {
            	            	String substring = className.substring(1 + previousIndex);
            	            	theType = ast.newQualifiedType(theType, ast.newSimpleName(substring));
            	            }
            	            formatedValue = theType;
            			}
            		}
    				MemberValuePair tmp = JDTUtils.newMemberValuePair(compilationUnitAstNode,
                                                                      attributeName,
                                                                      formatedValue);
    				Object[] arrayItem = (Object[])treeEditor.getItem().getParentItem().getData();
    				NormalAnnotation na = (NormalAnnotation)arrayItem[1];
    				List<MemberValuePair> list = na.values();

    				for (int i = 0; i < list.size(); i++) {
    					MemberValuePair item = (MemberValuePair)list.get(i);
    					if (item.getName().getIdentifier().equals(attributeName)) {
    						list.remove(i);
    						break;
    					}
    				}
    				list.add(tmp);
    				pair[1] = tmp;
            		annotationClass = (Class)treeEditor.getItem().getParentItem().getParentItem().getParentItem().getData();
            	} else if (pair[0] instanceof String) {
            		// handled elsewhere
            	}

            	Annotation annotationNode = annotationNodesMap.get(annotationClass.getSimpleName());
                NormalAnnotation normalAnnotationNode = (NormalAnnotation)annotationNode;

                ScJDTAnnUtils.addAnnotationToCu(compilationUnitMember,
                                                compilationUnitAstNode,
                                                normalAnnotationNode,
                                                selectedJdtMember,
                                                selectedJdtMethodParam);
            	refreshViewContent();
            }

        } catch (JavaModelException e) {
            // TODO notify the user ??
            LOG.error("Java model update failed: ", e);
        } catch (MalformedTreeException e) {
            // TODO notify the user ??
            LOG.error("Java model update failed: ", e);
        } catch (BadLocationException e) {
            // TODO notify the user ??
            LOG.error("Java model update failed: ", e);
        } catch (NoSuchMethodException e) {
            // TODO notify the user ??
            LOG.error("Java model update failed: ", e);
        }
    }

    private void processUpdateOnNumericAttribute() {
        String originalValue = treeEditor.getItem().getText(1);
        String updatedValue = text.getText();
        disposeEditWidgets();
        
        if (!originalValue.equals(updatedValue)) {
            treeEditor.getItem().setText(1, updatedValue);
            Double value = updatedValue.trim().length() > 0 ? new Double(updatedValue) : null;
            applyAnnotationAttributeUpdate(value);
        }
    }

    private void processUpdateOnTextAttribute() {
        originalValue = treeEditor.getItem().getText(1);
        String updatedValue = text.getText().trim().length() > 0 ? text.getText() : null;
        disposeEditWidgets();
        
        if (!originalValue.equals(updatedValue)
                && !(updatedValue == null && (originalValue == null || originalValue == ""))) {
            treeEditor.getItem().setText(1, updatedValue != null ? updatedValue : "");
            applyAnnotationAttributeUpdate(updatedValue);
        }
    }

    /**
     * listen to workbench selection events, pre-filter them and initiate view updates as appropriate,
     * meaning:
     * <br> - selection in the outline view if it contains javafile information
     * <br> - text selection within an editor opened on a java file
     */
    private class AnnotationSelectionListener implements INullSelectionListener {
        
        public void selectionChanged(IWorkbenchPart sourcepart, ISelection selection) {

            try {
                // we ignore our own selections
                if ((sourcepart == null) || (sourcepart == AnnotationView.this)) {
                    // self or blank selection: ignore
                    return;
                } else if ((sourcepart != AnnotationView.this)
                            && selection instanceof IStructuredSelection
                            && (((IStructuredSelection)selection).size() == 1)
                            && ((IStructuredSelection)selection).getFirstElement() instanceof IMember) {
    
                    // structured selection very probably from the outline view
                    Object firstElement = ((IStructuredSelection)selection).getFirstElement();
                    IMember javaMember = (IMember)firstElement;                    

                    IResource javaResource = javaMember.getResource();
                    
                    if (javaResource == null) {
                        updateViewContent(javaMember, -1, false);
                    } else {
                        IWorkbenchPage activePage = 
                            PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
                        int editorCount = activePage.findEditors(new FileEditorInput((IFile)(javaResource)),
                                                                 JAVA_EDITOR_ID,
                                                                 IWorkbenchPage.MATCH_INPUT 
                                                                     | IWorkbenchPage.MATCH_ID).length;
                        updateViewContent(javaMember, -1, editorCount > 0);
                    }                    
                
                } else if (selection instanceof ITextSelection) {
                    IWorkbenchWindow workbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
                    IWorkbenchPage activePage = workbenchWindow.getActivePage();
                    if (activePage == null) {
                        return;
                    }
                    IEditorPart editor = activePage.getActiveEditor();
    
                    if (editor == null
                            || (editor.getSite().getPart() != sourcepart)
                            || !(editor.getEditorInput() instanceof IFileEditorInput)) {
                        
                        // the selection data and the workench state aren't in synch: clear all
                        clearView();
                    }
    
                    javaEditorInputFile = ((IFileEditorInput)editor.getEditorInput()).getFile();
                    int offset = ((ITextSelection)selection).getOffset();
                    IJavaElement javaElement = JDTUtils.getJavaElementFromFile(javaEditorInputFile, offset);
                    if (javaElement == null || !(javaElement instanceof IMember)) {
                    	clearView();
                    } else {
                        updateViewContent((IMember)javaElement, offset, true);
                    }
                }
                
                //clearView();

            } catch (Exception e) {
                LOG.info("failed to handle selection change event", e); 
                clearView();
            }
        }
    }
    
    private class JavaEditorListener implements IPartListener2 {
        
        public void partActivated(IWorkbenchPartReference partRef) {
            LOG.debug(" part listener : 1");
            if (isEditorRelevant(partRef)) {
                editSupportEnabled = true;
                updateViewDescription();
            }
        }

        public void partBroughtToTop(IWorkbenchPartReference partRef) {
            // we don't care 
        }

        public void partClosed(IWorkbenchPartReference partRef) {
            LOG.debug(" part listener : 2");
            if (isEditorRelevant(partRef)) {
                editSupportEnabled = false;
                disposeEditWidgets();
                updateViewDescription();
            }
        }

        public void partDeactivated(IWorkbenchPartReference partRef) {
        }

        public void partHidden(IWorkbenchPartReference partRef) {
            // we don't care 
        }

        public void partInputChanged(IWorkbenchPartReference partRef) {
            LOG.debug(" part listener : 3");
            if (isEditorRelevant(partRef)) {
                clearView();
            }
        }

        public void partOpened(IWorkbenchPartReference partRef) {
            LOG.debug(" part listener : 4");
            if (isEditorRelevant(partRef)) {
                editSupportEnabled = true;
                updateViewDescription();
            }
        }

        public void partVisible(IWorkbenchPartReference partRef) {
            // we don't care 
        }
        
        private boolean isEditorRelevant(IWorkbenchPartReference partRef) {
            if (JAVA_EDITOR_ID.equals(partRef.getId())) {
                IWorkbenchPart part = partRef.getPart(false);
                if (part != null && part instanceof IEditorPart) {
                    IEditorPart javaEditor = (IEditorPart)part;
                    IFileEditorInput fileInput = (IFileEditorInput)javaEditor.getEditorInput();
                    boolean a = fileInput.getFile().equals(javaEditorInputFile);
                    LOG.debug(" isEditorRelevant : " + a);
                    return a;
                }
            }
            return false;
        }

    }
}
