/*
 * 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.debugger.jpda.projectsui;

import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner;
import java.awt.Color;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Function;
import java.util.logging.Logger;
import javax.lang.model.element.ElementKind;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import org.netbeans.api.debugger.Breakpoint;
import org.netbeans.api.debugger.Properties;
import org.netbeans.api.debugger.jpda.InvalidExpressionException;
import org.netbeans.api.debugger.jpda.JPDAThread;
import org.netbeans.api.debugger.jpda.LineBreakpoint;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.api.editor.settings.AttributesUtilities;
import org.netbeans.api.editor.settings.EditorStyleConstants;
import org.netbeans.api.editor.settings.FontColorSettings;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.editor.JumpList;
import org.netbeans.modules.debugger.jpda.projects.ASTOperationCreationDelegate;
import org.netbeans.modules.debugger.jpda.projects.EditorContextSupport;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.netbeans.modules.parsing.spi.ParseException;
import org.netbeans.modules.parsing.spi.Parser.Result;
import org.netbeans.spi.debugger.jpda.EditorContext;
import org.netbeans.spi.debugger.jpda.Evaluator.Expression;
import org.netbeans.spi.debugger.jpda.SourcePathProvider;
import org.netbeans.spi.debugger.ui.EditorContextDispatcher;
import org.openide.ErrorManager;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.URLMapper;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.text.Annotation;
import org.openide.text.Line;
import org.openide.text.Line.ShowOpenType;
import org.openide.text.Line.ShowVisibilityType;
import org.openide.text.NbDocument;
import org.openide.util.Pair;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;

/**
 *
 * @author Jan Jancura
 */
@EditorContext.Registration()
public class EditorContextImpl extends EditorContext {

    private static String fronting =
        System.getProperty ("netbeans.debugger.fronting", "true");
    
    private static final Logger logger = Logger.getLogger(EditorContextImpl.class.getName());

    private PropertyChangeSupport   pcs;
    private Map<Annotation, String> annotationToURL = new HashMap<Annotation, String>();
    private final Object annotationToURLLock = new Object();
    private PropertyChangeListener  dispatchListener;
    private EditorContextDispatcher contextDispatcher;

    {
        pcs = new PropertyChangeSupport (this);
        dispatchListener = new EditorContextDispatchListener();
        contextDispatcher = EditorContextDispatcher.getDefault();
        contextDispatcher.addPropertyChangeListener("text/x-java",
                WeakListeners.propertyChange(dispatchListener, contextDispatcher));
        
    }


    /**
     * Shows source with given url on given line number.
     *
     * @param url a url of source to be shown
     * @param lineNumber a number of line to be shown
     * @param timeStamp a time stamp to be used
     */
    @Override
    public boolean showSource (String url, int lineNumber, Object timeStamp) {
        Line l = showSourceLine(url, lineNumber, timeStamp);
        if (l != null) {
            addPositionToJumpList(url, l, 0);
        }
        return l != null;
    }

    static Line showSourceLine (String url, int lineNumber, Object timeStamp) {
        Line l = LineTranslations.getTranslations().getLine (url, lineNumber, timeStamp); // false = use original ln
        if (l == null) {
            ErrorManager.getDefault().log(ErrorManager.WARNING,
                    "Show Source: Have no line for URL = "+url+", line number = "+lineNumber);
            return null;
        }
        Properties p = Properties.getDefault().getProperties("debugger.options.JPDA");
        boolean reuseEditorTabs = p.getBoolean("ReuseEditorTabs", true);
        if ("true".equalsIgnoreCase(fronting)) {
            if (reuseEditorTabs) {
                l.show (ShowOpenType.REUSE, ShowVisibilityType.FOCUS);
            } else {
                l.show (ShowOpenType.OPEN, ShowVisibilityType.FOCUS);
            }
            l.show (ShowOpenType.OPEN, ShowVisibilityType.FRONT); //FIX 47825
        } else {
            if (reuseEditorTabs) {
                l.show (ShowOpenType.REUSE, ShowVisibilityType.FOCUS);
            } else {
                l.show (ShowOpenType.OPEN, ShowVisibilityType.FOCUS);
            }
        }
        return l;
    }

    /**
     * Shows source with given url on given line number.
     *
     * @param url a url of source to be shown
     * @param lineNumber a number of line to be shown
     * @param timeStamp a time stamp to be used
     */
    public boolean showSource (String url, int lineNumber, int column, int length, Object timeStamp) {
        Line l = LineTranslations.getTranslations().getLine (url, lineNumber, timeStamp); // false = use original ln
        if (l == null) {
            ErrorManager.getDefault().log(ErrorManager.WARNING,
                    "Show Source: Have no line for URL = "+url+", line number = "+lineNumber);
            return false;
        }
        if ("true".equalsIgnoreCase(fronting)) {
            l.show (ShowOpenType.OPEN, ShowVisibilityType.FRONT, column); //FIX 47825
        } else {
            l.show (ShowOpenType.OPEN, ShowVisibilityType.FOCUS, column);
        }
        addPositionToJumpList(url, l, column);
        return true;
    }

    /** Add the line offset into the jump history */
    private void addPositionToJumpList(String url, Line l, int column) {
        DataObject dataObject = getDataObject (url);
        if (dataObject != null) {
            EditorCookie ec = dataObject.getLookup().lookup(EditorCookie.class);
            if (ec != null) {
                try {
                    StyledDocument doc = ec.openDocument();
                    JEditorPane[] eps = ec.getOpenedPanes();
                    if (eps != null && eps.length > 0) {
                        JumpList.addEntry(eps[0], NbDocument.findLineOffset(doc, l.getLineNumber()) + column);
                    }
                } catch (java.io.IOException ioex) {
                    ErrorManager.getDefault().notify(ioex);
                }
            }
        }
    }


    /**
     * Creates a new time stamp.
     *
     * @param timeStamp a new time stamp
     */
    @Override
    public void createTimeStamp (Object timeStamp) {
        LineTranslations.getTranslations().createTimeStamp(timeStamp);
    }

    /**
     * Disposes given time stamp.
     *
     * @param timeStamp a time stamp to be disposed
     */
    @Override
    public void disposeTimeStamp (Object timeStamp) {
        LineTranslations.getTranslations().disposeTimeStamp(timeStamp);
    }

    @Override
    public Object annotate (
        String url,
        int lineNumber,
        String annotationType,
        Object timeStamp
    ) {
        return annotate(url, lineNumber, annotationType, timeStamp, null);
    }
    @Override
    public Object annotate (
        String url,
        int lineNumber,
        String annotationType,
        Object timeStamp,
        JPDAThread thread
    ) {
        Line l =  LineTranslations.getTranslations().getLine (
            url,
            lineNumber,
            (timeStamp instanceof Breakpoint) ? null : timeStamp
        );
        if (l == null) {
            return null;
        }
        Annotation annotation;
        if (timeStamp instanceof Breakpoint) {
            annotation = new DebuggerBreakpointAnnotation(annotationType, l, (Breakpoint) timeStamp);
        } else {
            annotation = new DebuggerAnnotation (annotationType, l, thread);
        }
        synchronized (annotationToURLLock) {
            assert url != null;
            annotationToURL.put (annotation, url);
        }
        return annotation;
    }

    @Override
    public Object annotate (
        String url,
        int startPosition,
        int endPosition,
        String annotationType,
        Object timeStamp
    ) {
        AttributeSet attrs;
        if (EditorContext.CURRENT_LAST_OPERATION_ANNOTATION_TYPE.equals(annotationType)) {
            attrs = AttributesUtilities.createImmutable(EditorStyleConstants.WaveUnderlineColor, getColor(annotationType));
        } else {
            attrs = AttributesUtilities.createImmutable(StyleConstants.Background, getColor(annotationType));
        }
        DebuggerAnnotation annotation;
        try {
            annotation = new DebuggerAnnotation(annotationType, attrs, startPosition, endPosition,
                    URLMapper.findFileObject(new URL(url)));
        } catch (MalformedURLException ex) {
            throw new RuntimeException("Bad URL: "+url, ex);
        }
        synchronized (annotationToURLLock) {
            assert url != null;
            annotationToURL.put (annotation, url);
        }
        return annotation;
    }

    private static Color getColor(String annotationType) {
        if (annotationType.endsWith("_broken")) {
            annotationType = annotationType.substring(0, annotationType.length() - "_broken".length());
        }
        if (EditorContext.BREAKPOINT_ANNOTATION_TYPE.equals(annotationType)) {
            return getHighlight(annotationType, 0xFC9D9F);
        } else if (EditorContext.CURRENT_LINE_ANNOTATION_TYPE.equals(annotationType) ||
                   EditorContext.CURRENT_OUT_OPERATION_ANNOTATION_TYPE.equals(annotationType)) {
            return getHighlight(annotationType, 0xBDE6AA);
        //} else if (EditorContext.CURRENT_EXPRESSION_CURRENT_LINE_ANNOTATION_TYPE.equals(annotationType)) {
        //    return getHighlight(annotationType, 0xE9FFE6); // 0xE3FFD2// 0xD1FFBC
        } else if (EditorContext.CURRENT_LAST_OPERATION_ANNOTATION_TYPE.equals(annotationType)) {
            return getHighlight(annotationType, 0x99BB8A);
        } else {
            return new Color(0x0000FF);
        }
    }

    private static Color getHighlight(String name, int defaultRGB) {
        FontColorSettings fcs = MimeLookup.getLookup(MimePath.EMPTY).lookup(FontColorSettings.class);
        AttributeSet as = (fcs != null) ? fcs.getFontColors(name) : null;
        if (as != null) {
            return (Color) as.getAttribute(StyleConstants.Background);
        } else {
            return new Color(defaultRGB);
        }
    }

    /**
     * Removes given annotation.
     *
     * @return true if annotation has been successfully removed
     */
    @Override
    public void removeAnnotation (
        Object a
    ) {
        if (a instanceof Collection) {
            Collection annotations = ((Collection) a);
            for (Iterator it = annotations.iterator(); it.hasNext(); ) {
                removeAnnotation((Annotation) it.next());
            }
        } else {
            removeAnnotation((Annotation) a);
        }
    }

    private void removeAnnotation(Annotation annotation) {
        synchronized (annotationToURLLock) {
            String url = annotationToURL.remove (annotation);
            //logger.severe("Removing "+annotation+", URL = "+url+", thread = "+Thread.currentThread().getId());
            //Thread.dumpStack();
            assert url != null;
        }
        annotation.detach ();
        
    }

    /**
     * Returns line number given annotation is associated with.
     *
     * @param annotation an annotation, or an array of "url" and new Integer(line number)
     * @param timeStamp a time stamp to be used
     *
     * @return line number given annotation is associated with
     */
    @Override
    public int getLineNumber (
        Object annotation,
        Object timeStamp
    ) {
        if (annotation instanceof LineBreakpoint) {
            // A sort of hack to be able to retrieve the original line.
            LineBreakpoint lb = (LineBreakpoint) annotation;
            return LineTranslations.getTranslations().getOriginalLineNumber(lb, timeStamp);
        }
        /*if (annotation instanceof Object[]) {
            // A sort of hack to be able to retrieve the original line.
            Object[] urlLine = (Object[]) annotation;
            String url = (String) urlLine[0];
            int line = ((Integer) urlLine[1]).intValue();
            return LineTranslations.getTranslations().getOriginalLineNumber(url, line, timeStamp);
        }*/
        Line line;
        if (annotation instanceof DebuggerBreakpointAnnotation) {
            line = ((DebuggerBreakpointAnnotation) annotation).getLine();
        } else {
            line = ((DebuggerAnnotation) annotation).getLine();
        }
        if (timeStamp == null) {
            return line.getLineNumber () + 1;
        }
        String url;
        synchronized (annotationToURLLock) {
            url = annotationToURL.get ((Annotation) annotation);
            assert url != null;
        }
        Line.Set lineSet = LineTranslations.getTranslations().getLineSet (url, timeStamp);
        return lineSet.getOriginalLineNumber (line) + 1;
    }

    /**
     * Updates timeStamp for gived url.
     *
     * @param timeStamp time stamp to be updated
     * @param url an url
     */
    @Override
    public void updateTimeStamp (Object timeStamp, String url) {
        LineTranslations.getTranslations().updateTimeStamp(timeStamp, url);
    }

    /**
     * Returns number of line currently selected in editor or <code>-1</code>.
     *
     * @return number of line currently selected in editor or <code>-1</code>
     */
    @Override
    public int getCurrentLineNumber () {
        return contextDispatcher.getCurrentLineNumber();
    }

    /**
     * Returns number of line currently selected in editor or <code>-1</code>.
     *
     * @return number of line currently selected in editor or <code>-1</code>
     */
    public int getCurrentOffset () {
        JEditorPane ep = contextDispatcher.getCurrentEditor();
        if (ep == null) {
            return -1;
        }
        Caret caret = ep.getCaret ();
        if (caret == null) {
            return -1;
        }
        return caret.getDot();
    }

    /**
     * Returns name of class currently selected in editor or empty string.
     *
     * @return name of class currently selected in editor or empty string
     */
    @Override
    public String getCurrentClassName () {
        String currentClass = getCurrentElement(ElementKind.CLASS);
        if (currentClass == null) {
            return "";
        } else {
            return currentClass;
        }
    }

    /**
     * Returns name of class recently selected in editor or empty string.
     *
     * @return name of class recently selected in editor or empty string
     */
    public String getMostRecentClassName () {
        String clazz = getMostRecentElement(ElementKind.CLASS);
        if (clazz == null) {
            return "";
        } else {
            return clazz;
        }
    }

    /**
     * Returns URL of source currently selected in editor or empty string.
     *
     * @return URL of source currently selected in editor or empty string
     */
    @Override
    public String getCurrentURL () {
        return contextDispatcher.getCurrentURLAsString();
    }

    /**
     * Returns name of method currently selected in editor or empty string.
     *
     * @return name of method currently selected in editor or empty string
     */
    @Override
    public String getCurrentMethodName () {
        String currentMethod = getCurrentElement(ElementKind.METHOD);
        if (currentMethod == null) {
            return "";
        } else {
            return currentMethod;
        }
    }

    /**
     * Returns name of method recently selected in editor or empty string.
     *
     * @return name of method recently selected in editor or empty string
     */
    public String getMostRecentMethodName () {
        String method = getMostRecentElement(ElementKind.METHOD);
        if (method == null) {
            return "";
        } else {
            return method;
        }
    }

    /**
     * Returns signature of method currently selected in editor or null.
     *
     * @return signature of method currently selected in editor or null
     */
    public String getCurrentMethodSignature () {
        final String[] elementSignaturePtr = new String[] { null };
        try {
            getCurrentElement(ElementKind.METHOD, elementSignaturePtr);
        } catch (final java.awt.IllegalComponentStateException icse) {
            throw new java.awt.IllegalComponentStateException() {
                @Override
                public String getMessage() {
                    icse.getMessage();
                    return elementSignaturePtr[0];
                }
            };
        }
        return elementSignaturePtr[0];
    }

    public String getMostRecentMethodSignature () {
        final String[] elementSignaturePtr = new String[] { null };
        try {
            getMostRecentElement(ElementKind.METHOD, elementSignaturePtr);
        } catch (final java.awt.IllegalComponentStateException icse) {
            throw new java.awt.IllegalComponentStateException() {
                @Override
                public String getMessage() {
                    icse.getMessage();
                    return elementSignaturePtr[0];
                }
            };
        }
        return elementSignaturePtr[0];
    }

    /**
     * Returns name of field currently selected in editor or <code>null</code>.
     *
     * @return name of field currently selected in editor or <code>null</code>
     */
    @Override
    public String getCurrentFieldName () {
        String currentField = getCurrentElement(ElementKind.FIELD);
        if (currentField == null) {
            return "";
        } else {
            return currentField;
        }
        //return getSelectedIdentifier ();
    }

    /**
     * Returns name of field recently selected in editor or <code>null</code>.
     *
     * @return name of field recently selected in editor or <code>null</code>
     */
    public String getMostRecentFieldName () {
        String field = getMostRecentElement(ElementKind.FIELD);
        if (field == null) {
            return "";
        } else {
            return field;
        }
    }


    /**
     * Returns identifier currently selected in editor or <code>null</code>.
     *
     * @return identifier currently selected in editor or <code>null</code>
     */
    @Override
    public String getSelectedIdentifier () {
        JEditorPane ep = contextDispatcher.getCurrentEditor ();
        if (ep == null) {
            return null;
        }
        Caret caret = ep.getCaret();
        if (caret == null) {
            // No caret => no selected text
            return null;
        }
        String s = ep.getSelectedText ();
        if (s == null) {
            return null;
        }
        if (Utilities.isJavaIdentifier (s)) {
            return s;
        }
        return null;
    }

    /**
     * Returns method name currently selected in editor or empty string.
     *
     * @return method name currently selected in editor or empty string
     */
    @Override
    public String getSelectedMethodName () {
        if (SwingUtilities.isEventDispatchThread()) {
            return getSelectedMethodName_();
        } else {
            final String[] mn = new String[1];
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    @Override
                    public void run() {
                        mn[0] = getSelectedMethodName_();
                    }
                });
            } catch (InvocationTargetException ex) {
                ErrorManager.getDefault().notify(ex.getTargetException());
            } catch (InterruptedException ex) {
                // interrupted, ignored.
            }
            return mn[0];
        }
    }

    private String getSelectedMethodName_() {
        JEditorPane ep = contextDispatcher.getCurrentEditor ();
        if (ep == null) {
            return "";
        }
        StyledDocument doc = (StyledDocument) ep.getDocument ();
        if (doc == null) {
            return "";
        }
        int offset = ep.getCaret ().getDot ();
        String t;
//        if ( (ep.getSelectionStart () <= offset) &&
//             (offset <= ep.getSelectionEnd ())
//        )   t = ep.getSelectedText ();
//        if (t != null) return t;

        int line = NbDocument.findLineNumber (
            doc,
            offset
        );
        int col = NbDocument.findLineColumn (
            doc,
            offset
        );
        try {
            javax.swing.text.Element lineElem =
                org.openide.text.NbDocument.findLineRootElement (doc).
                getElement (line);

            if (lineElem == null) {
                return "";
            }
            int lineStartOffset = lineElem.getStartOffset ();
            int lineLen = lineElem.getEndOffset () - lineStartOffset;
            // t contains current line in editor
            t = doc.getText (lineStartOffset, lineLen);

            int identStart = col;
            while ( identStart > 0 &&
                    Character.isJavaIdentifierPart (
                        t.charAt (identStart - 1)
                    )
            ) {
                identStart--;
            }

            int identEnd = col;
            while (identEnd < lineLen &&
                   Character.isJavaIdentifierPart (t.charAt (identEnd))
            ) {
                identEnd++;
            }
            int i = t.indexOf ('(', identEnd);
            if (i < 0) {
                return "";
            }
            if (t.substring (identEnd, i).trim ().length () > 0) {
                return "";
            }

            if (identStart == identEnd) {
                return "";
            }
            return t.substring (identStart, identEnd);
        } catch (javax.swing.text.BadLocationException ex) {
            return "";
        }
    }

    /**
     * Returns line number of given field in given class.
     *
     * @param url the url of file the class is deined in
     * @param className the name of class (or innerclass) the field is
     *                  defined in
     * @param fieldName the name of field
     *
     * @return line number or -1
     */
    @Override
    public int getFieldLineNumber (
        String url,
        final String className,
        final String fieldName
    ) {
        return EditorContextSupport.getFieldLineNumber(url, className, fieldName);
    }

    /**
     * Returns line number of given method in given class.
     *
     * @param url the url of file the class is deined in
     * @param className the name of class (or innerclass) the method is
     *                  defined in
     * @param methodName the name of method
     * @param methodSignature the JNI-style signature of the method.
     *        If <code>null</code>, then the first method found is returned.
     *
     * @return line number or -1
     */
    @Override
    public int getMethodLineNumber (
        String url,
        final String className,
        final String methodName,
        final String methodSignature
    ) {
        return EditorContextSupport.getMethodLineNumber(url, className, methodName, methodSignature);
    }

    /** @return declared class name
     */
    public String getCurrentClassDeclaration() {
        FileObject fo = contextDispatcher.getCurrentFile();
        if (fo == null) {
            return null;
        }
        JEditorPane ep = contextDispatcher.getCurrentEditor();
        final int currentOffset = (ep == null) ? 0 : ep.getCaretPosition();
        //final int currentOffset = org.netbeans.editor.Registry.getMostActiveComponent().getCaretPosition();
        
        return EditorContextSupport.getClassDeclaredAt(fo, currentOffset);
    }

    /** @return { "method name", "method signature", "enclosing class name" }
     */
    @Override
    public String[] getCurrentMethodDeclaration() {
        FileObject fo = contextDispatcher.getCurrentFile();
        if (fo == null) {
            return null;
        }
        JEditorPane ep = contextDispatcher.getCurrentEditor();
        final int currentOffset = (ep == null) ? 0 : ep.getCaretPosition();
        return EditorContextSupport.getMethodDeclaredAt(fo, currentOffset);
    }


    /**
     * Returns binary class name for given url and line number or null.
     *
     * @param url a url
     * @param lineNumber a line number
     *
     * @return binary class name for given url and line number or null
     */
    @Override
    public String getClassName (
        String url,
        int lineNumber
    ) {
        return EditorContextSupport.getClassName(url, lineNumber);
    }

    @Override
    public Operation[] getOperations(String url, final int lineNumber,
                                     final BytecodeProvider bytecodeProvider) {
        return EditorContextSupport.getOperations(url, lineNumber,
                                                  bytecodeProvider,
                                                  new OperationCreationDelegateImpl());
    }

    /** return the offset of the first non-whitespace character on the line,
               or -1 when the line does not exist
     */
    private static int findLineOffset(StyledDocument doc, int lineNumber) {
        int offset;
        try {
            offset = NbDocument.findLineOffset (doc, lineNumber - 1);
            int offset2 = NbDocument.findLineOffset (doc, lineNumber);
            try {
                String lineStr = doc.getText(offset, offset2 - offset);
                for (int i = 0; i < lineStr.length(); i++) {
                    if (!Character.isWhitespace(lineStr.charAt(i))) {
                        offset += i;
                        break;
                    }
                }
            } catch (BadLocationException ex) {
                // ignore
            }
        } catch (IndexOutOfBoundsException ioobex) {
            return -1;
        }
        return offset;
    }

    @Override
    public MethodArgument[] getArguments(String url, final Operation operation) {
        return EditorContextSupport.getArguments(url, operation, new OperationCreationDelegateImpl());
    }
    
    @Override
    public MethodArgument[] getArguments(String url, final int methodLineNumber) {
        return EditorContextSupport.getArguments(url, methodLineNumber, new OperationCreationDelegateImpl());
    }
    
    /**
     * Returns list of imports for given source url.
     *
     * @param url the url of source file
     *
     * @return list of imports for given source url
     */
    @Override
    public String[] getImports (
        String url
    ) {
        return EditorContextSupport.getImports(url);
    }
    
    public <R,D> R interpretOrCompileCode(final Expression<Object> expression, String url, final int line,
                                          final ErrorAwareTreePathScanner<Boolean,D> canInterpret,
                                          final ErrorAwareTreePathScanner<R,D> interpreter,
                                          final D context, final boolean staticContext,
                                          final Function<Pair<String, byte[]>, Boolean> compiledClassHandler,
                                          final SourcePathProvider sp) throws InvalidExpressionException {
        return EditorContextSupport.interpretOrCompileCode(expression, url, line,
                                                           canInterpret,
                                                           interpreter,
                                                           context, staticContext,
                                                           compiledClassHandler,
                                                           sp);
    }

    /**
     * Adds a property change listener.
     *
     * @param l the listener to add
     */
    @Override
    public void addPropertyChangeListener (PropertyChangeListener l) {
        pcs.addPropertyChangeListener (l);
    }

    /**
     * Removes a property change listener.
     *
     * @param l the listener to remove
     */
    @Override
    public void removePropertyChangeListener (PropertyChangeListener l) {
        pcs.removePropertyChangeListener (l);
    }

    /**
     * Adds a property change listener.
     *
     * @param propertyName the name of property
     * @param l the listener to add
     */
    @Override
    public void addPropertyChangeListener (
        String propertyName,
        PropertyChangeListener l
    ) {
        pcs.addPropertyChangeListener (propertyName, l);
    }

    /**
     * Removes a property change listener.
     *
     * @param propertyName the name of property
     * @param l the listener to remove
     */
    @Override
    public void removePropertyChangeListener (
        String propertyName,
        PropertyChangeListener l
    ) {
        pcs.removePropertyChangeListener (propertyName, l);
    }


    // private helper methods ..................................................

//    public void fileChanged (FileEvent fe) {
//	pcs.firePropertyChange (PROP_LINE_NUMBER, null, null);
//    }
//
//    public void fileDeleted (FileEvent fe) {}
//    public void fileAttributeChanged (org.openide.filesystems.FileAttributeEvent fe) {}
//    public void fileDataCreated (FileEvent fe) {}
//    public void fileFolderCreated (FileEvent fe) {}
//    public void fileRenamed (org.openide.filesystems.FileRenameEvent fe) {}


    private String getCurrentElement(ElementKind kind) {
        return getCurrentElement(kind, null);
    }

    private String getMostRecentElement(ElementKind kind) {
        return getMostRecentElement(kind, null);
    }

    /** throws IllegalComponentStateException when can not return the data in AWT. */
    private String getCurrentElement(final ElementKind kind, final String[] elementSignaturePtr)
            throws java.awt.IllegalComponentStateException {
        return getCurrentElement(contextDispatcher.getCurrentFile(),
                                 contextDispatcher.getCurrentEditor(),
                                 kind, elementSignaturePtr);
    }

    /** throws IllegalComponentStateException when can not return the data in AWT. */
    private String getMostRecentElement(final ElementKind kind, final String[] elementSignaturePtr)
            throws java.awt.IllegalComponentStateException {
        return getCurrentElement(contextDispatcher.getMostRecentFile(),
                                 contextDispatcher.getMostRecentEditor(),
                                 kind, elementSignaturePtr);
    }

    /** throws IllegalComponentStateException when can not return the data in AWT. */
    private String getCurrentElement(FileObject fo, JEditorPane ep,
                                     final ElementKind kind, final String[] elementSignaturePtr)
            throws java.awt.IllegalComponentStateException {

        if (fo == null) {
            return null;
        }
        final int currentOffset;
        final String selectedIdentifier;
        if (ep != null) {
            String s;
            Caret caret = ep.getCaret();
            if (caret == null) {
                s = null;
                currentOffset = 0;
            } else {
                s = ep.getSelectedText ();
                currentOffset = ep.getCaretPosition();
                if (ep.getSelectionStart() > currentOffset || ep.getSelectionEnd() < currentOffset) {
                    s = null; // caret outside of the selection
                }
            }
            if (s != null && Utilities.isJavaIdentifier (s)) {
                selectedIdentifier = s;
            } else {
                selectedIdentifier = null;
            }
        } else {
            selectedIdentifier = null;
            currentOffset = 0;
        }
        return EditorContextSupport.getCurrentElement(fo, currentOffset, selectedIdentifier, kind, elementSignaturePtr);
    }

    private static DataObject getDataObject (String url) {
        FileObject file;
        try {
            file = URLMapper.findFileObject (new URL (url));
        } catch (MalformedURLException e) {
            return null;
        }

        if (file == null) {
            return null;
        }
        try {
            return DataObject.find (file);
        } catch (DataObjectNotFoundException ex) {
            return null;
        }
    }

    private static StyledDocument findDocument(FileObject fo) {
        DataObject dataObject;
        try {
            dataObject = DataObject.find (fo);
        } catch (DataObjectNotFoundException ex) {
            return null;
        }
        EditorCookie ec = (EditorCookie) dataObject.getLookup().lookup(EditorCookie.class);
        if (ec == null) {
            return null;
        }
        StyledDocument doc;
        try {
            doc = ec.openDocument();
        } catch (IOException ex) {
            return null;
        }
        return doc;
    }

    private static StyledDocument findDocument(DataObject dataObject) {
        EditorCookie ec = (EditorCookie) dataObject.getLookup().lookup(EditorCookie.class);
        if (ec == null) {
            return null;
        }
        StyledDocument doc;
        try {
            doc = ec.openDocument();
        } catch (IOException ex) {
            return null;
        }
        return doc;
    }

    private static CompilationController retrieveController(ResultIterator resIt, StyledDocument doc) throws ParseException {
        Result res = resIt.getParserResult();
        CompilationController ci = res != null ? CompilationController.get(res) : null;
        if (ci == null) {
            ErrorManager.getDefault().log(ErrorManager.WARNING,
                    "Unable to get compilation controller " + doc);
        }
        return ci;
    }

    // Support classes:

    private class EditorContextDispatchListener extends Object implements PropertyChangeListener {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            pcs.firePropertyChange (org.openide.windows.TopComponent.Registry.PROP_CURRENT_NODES, null, null);
        }

    }

    private class OperationCreationDelegateImpl implements ASTOperationCreationDelegate {
        /*
         public Operation createOperation(
                 Position startPosition,
                 Position endPosition,
                 int bytecodeIndex) {
             return EditorContextImpl.this.createOperation(
                     startPosition,
                     endPosition,
                     bytecodeIndex);
         }
         */
        @Override
         public Operation createMethodOperation(
                 Position startPosition,
                 Position endPosition,
                 Position methodStartPosition,
                 Position methodEndPosition,
                 String methodName,
                 String methodClassType,
                 int bytecodeIndex,
                 boolean isNative) {
             return EditorContextImpl.this.createMethodOperation(
                     startPosition,
                     endPosition,
                     methodStartPosition,
                     methodEndPosition,
                     methodName,
                     methodClassType,
                     bytecodeIndex, isNative);
         }
        @Override
         public Position createPosition(
                 int offset,
                 int line,
                 int column) {
             return EditorContextImpl.this.createPosition(
                     offset,
                     line,
                     column);
         }
        @Override
         public void addNextOperationTo(Operation operation, Operation next) {
             EditorContextImpl.this.addNextOperationTo(operation, next);
         }
    }

}
