Package org.openquark.gems.client

Source Code of org.openquark.gems.client.IntellicutListModelAdapter$IntellicutListEntry

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Business Objects nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

/*
* IntellicutListModelAdapter.java
* Creation date: Dec 12, 2003
* By: Frank Worsley
*/
package org.openquark.gems.client;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.swing.JComponent;

import org.openquark.cal.compiler.FieldName;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy;
import org.openquark.cal.compiler.SourceMetrics;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy.UnqualifiedUnlessAmbiguous;
import org.openquark.cal.compiler.TypeChecker.TypeCheckInfo;
import org.openquark.cal.services.CALWorkspace;
import org.openquark.cal.services.GemEntity;
import org.openquark.cal.services.GemViewer;
import org.openquark.cal.services.MetaModule;
import org.openquark.cal.services.Perspective;
import org.openquark.cal.services.UnorderedTypeMatchFilter;
import org.openquark.gems.client.AutoburnLogic.AutoburnUnifyStatus;
import org.openquark.gems.client.AutoburnLogic.BurnCombination;
import org.openquark.gems.client.IntellicutListModel.FilterLevel;
import org.openquark.gems.client.IntellicutManager.IntellicutInfo;

/**
* An adapter class for the IntellicutListModel. Specific implementations of an adapter derive
* from this class and implement the abstract methods. This class allows the IntellicutListModel
* to be customized to work with additional data types.
* @author Frank Worsley
*/
public abstract class IntellicutListModelAdapter {
   
    /**
     * Comparator class used to do a compare between IntellicutListEntry objects.
     */
    private static class IntellicutListEntryComparator implements Comparator<Object> {
       
        private static final CaseInsensitiveStringComparator stringComparator = new CaseInsensitiveStringComparator();
       
        /**
         * Case insensitive string comparator. This is identical to the case insensitive
         * comparator in the java.lang.String class, except that it bumps the importance
         * of strings beginning with "{" to be ordered right after strings beginning with "(".
         */
        private static class CaseInsensitiveStringComparator implements Comparator<String> {
           
            public int compare(String s1, String s2) {
                int n1 = s1.length(), n2 = s2.length();
               
                if (n1 > 0 && n2 > 0) {
                   
                    if (s1.charAt(0) == '{') {
                        char c2 = s2.charAt(0);
                        if (c2 <= '(') {
                            return 1;
                           
                        } else if (c2 != '{') {
                            return -1;
                           
                        } else {
                            s1 = s1.substring(1);
                            s2 = s2.substring(1);
                            n1--;
                            n2--;
                        }
                       
                    } else if (s2.charAt(0) == '{') {
                       
                        char c1 = s1.charAt(0);
                        if (c1 <= '(') {
                            return -1;
                           
                        } else if (c1 != '{') {
                            return 1;
                           
                        } else {
                            s1 = s1.substring(1);
                            s2 = s2.substring(1);
                            n1--;
                            n2--;
                        }
                    }
                }
               
                for (int i1 = 0, i2 = 0; i1 < n1 && i2 < n2; i1++, i2++) {
                    char c1 = s1.charAt(i1);
                    char c2 = s2.charAt(i2);
                    if (c1 != c2) {
                        c1 = Character.toUpperCase(c1);
                        c2 = Character.toUpperCase(c2);
                        if (c1 != c2) {
                            c1 = Character.toLowerCase(c1);
                            c2 = Character.toLowerCase(c2);
                            if (c1 != c2) {
                                return c1 - c2;
                            }
                        }
                    }
                }
                return n1 - n2;
            }
        }
       
        /** Map to hold ordering of intellicut list entry classes */
        private static final Map<String, Integer> classOrderMap = new HashMap<String, Integer>();
        static {
            classOrderMap.put(   GemEntityListEntry.class.toString(), Integer.valueOf(0));
            classOrderMap.put(CollectorGemListEntry.class.toString(), Integer.valueOf(1));
            classOrderMap.put(    TypeExprListEntry.class.toString(), Integer.valueOf(2));
        }
       
        /**
         * Compares the list entry objects.
         * The entries are first ordered by alphabetically ascending sort strings,
         * sub-ordered by alphabetically ascending display strings, sub-ordered by
         * class type and contents.
         * All string comparisons are case insensitive, and the class orderings are
         * defined in the classOrderMap.
         *
         * @param o1
         * @param o2
         * @return negative, zero, or positive integer, when the first object
         *         is prior to, the same as, or greater than the second object
         */
        public int compare (Object o1, Object o2) {
           
            // Compare sorted strings
            String s1 = ((IntellicutListEntry) o1).getSortString();
            String s2 = ((IntellicutListEntry) o2).getSortString();
            int c = stringComparator.compare(s1, s2);
           
            if (c == 0) {
                // Sorted strings are equal, so compare their display strings
                s1 = ((IntellicutListEntry) o1).getDisplayString();
                s2 = ((IntellicutListEntry) o2).getDisplayString();
                c = stringComparator.compare(s1, s2);
            }
           
            if (c == 0) {
                // Display strings are equal, so compare their secondary display strings
                s1 = ((IntellicutListEntry) o1).getSecondaryDisplayString();
                s2 = ((IntellicutListEntry) o2).getSecondaryDisplayString();
                c = stringComparator.compare(s1, s2);
            }
           
            if (c == 0) {
                // Secondary display strings are equal, so compare the classes
                c = classOrderMap.get(o1.getClass().toString()).compareTo(classOrderMap.get(o2.getClass().toString()));
            }
           
            if (c == 0) {
                // Classes are equal, so compare their string representations
                c = o1.toString().compareTo(o2.toString());
            }
           
            return c;
        }
    }
   
    /**
     * This class represents an entry in the intellicut model. It encapsulates the data object
     * this entry represents and the intellicut info of that data object.
     * @author Frank Worsley
     */
    public static abstract class IntellicutListEntry {

        /** The data object that this list entry encapsulates. */
        private final Object dataObject;
       
        /** The intellicut info associated with this list entry. */
        private IntellicutInfo intellicutInfo;
       
        /**
         * Constructor for a new IntellicutListEntry object.
         * @param dataObject the data object this list entry is for
         */
        public IntellicutListEntry(Object dataObject) {
           
            if (dataObject == null) {
                throw new NullPointerException();
            }
           
            this.dataObject = dataObject;
        }
       
        /**
         * @return the data object held onto by this list entry
         */
        public final Object getData() {
            return dataObject;
        }
       
        /**
         * @param intellicutInfo the intellicut info for this list entry (must be non-null)
         */
        public void setIntellicutInfo(IntellicutInfo intellicutInfo) {
           
            if (intellicutInfo == null) {
                throw new NullPointerException();
            }
           
            this.intellicutInfo = intellicutInfo;
        }
       
        /**
         * @return the intellicut info for this list entry
         */
        public IntellicutInfo getIntellicutInfo() {
            return intellicutInfo;
        }
       
        /**
         * @return the string representation of the entry as it should be displayed in the list.
         */
        public abstract String getDisplayString();

        /**
         * @return the string representation of the entry as it should be written in the source code.
         */
        public String getSourceText(){       
            String sourceText = getSourceText();
            Object entryData = getData();
            if (entryData instanceof GemEntity) {
                if (!getSecondaryDisplayString().equals("")) {
                    // Secondary display string exists; this indicates the module for an ambiguous entry
                    sourceText = ((GemEntity)entryData).getName().getQualifiedName();
                }
            }
            return sourceText;
        }

        /**
         * @return a string containing additional information which is subjected to
         *         special formatting when displayed in the list.  Never null.
         */
        public String getSecondaryDisplayString() {
            return "";
        }
       
        /**
         * @return the string to use for sorting this entry in the intellicut list. This should
         * always be the unqualified name of the entry. This name is also used for narrowing
         * the intellicut list if the user types.
         */
        public abstract String getSortString();
       
        /**
         * @return the result type of the stored object or null if there is no result type.
         */
        public abstract TypeExpr getResultType();
       
        /**
         * @return the type expression of the stored object or null if there is no type expression
         */
        public abstract TypeExpr getTypeExpr();
       
        /**
         * @return the type pieces of the stored object to use for autoburning or null if
         * there are no such type pieces.
         */
        public abstract TypeExpr[] getTypePiecesForBurning();
       
        /**
         * @return the argument type pieces of the stored object or null if there are no such type pieces.
         */
        public abstract TypeExpr[] getArgumentTypePieces();
       
        /**
         * @return true if the stored object has any disconnected inputs, false if all inputs
         * are connected or if there are no inputs at all.
         */
        public abstract boolean hasDisconnectedInputs();

        /**
         * @return whether autoburning should be attempted on the stored object
         */
        public abstract boolean isAutoBurnable();
       
        /**
         * We need to override equals so we can find list entries after the list has been
         * reloaded and restore the list position. Two list entries are equal if they
         * have the same data object.
         * @param o the object to compare this list entry with
         * @return true if this list entry equals the list entry referenced by object o
         */
        @Override
        public boolean equals(Object o) {
           
            if (o == this) {
                return true;
            }
           
            if (o instanceof IntellicutListEntry) {
               
                IntellicutListEntry listEntry = (IntellicutListEntry) o;
               
                if (listEntry.getData().equals(dataObject)) {
                    return true;
                }
            }
           
            return false;
        }
       
        /**
         * The hashcode for a list entry is the hash code of its stored data object
         * This is overridden so that it corresponds with the equals implementation.
         * @return the hash code for this list entry
         */
        @Override
        public int hashCode() {
            return dataObject.hashCode();
        }

        /**
         * Don't remove this. It's used by the IntellicutComboBox to display the text of an
         * entry in the combobox text field.
         * @return the String representation of the list entry as it should be displayed in the list
         */
        @Override
        public String toString() {
            return getDisplayString();
        }
    }
   
    /**
     * A list entry implementation for a GemEntity.
     * @author Frank Worsley
     */
    private static class GemEntityListEntry extends IntellicutListEntry {
       
        /** The naming policy to use when displaying the entity name. */
        private ScopedEntityNamingPolicy namingPolicy;
       
        public GemEntityListEntry(GemEntity entity, ScopedEntityNamingPolicy namingPolicy) {
            super(entity);
           
            if (namingPolicy == null) {
                throw new NullPointerException();
            }
           
            this.namingPolicy = namingPolicy;
        }
       
        public GemEntity getEntity() {
            return (GemEntity) getData();
        }
       
        @Override
        public String getDisplayString() {
            return getEntity().getName().getUnqualifiedName();
        }

        @Override
        public String getSourceText(){
            return getEntity().getAdaptedName(namingPolicy);
        }
       
        /**
         * {@inheritDoc}
         */
        @Override
        public String getSecondaryDisplayString() {
            String adaptedName = getEntity().getAdaptedName(namingPolicy);
            if (QualifiedName.isValidCompoundName(adaptedName)) {
               
                // Adapted name is fully qualified, so entry is ambiguous
                // (use module as secondary display string)
                QualifiedName qualifiedName = QualifiedName.makeFromCompoundName(adaptedName);
                return qualifiedName.getModuleName().toSourceText();
               
            } else {
                // Unqualified name; no additional info
                return "";
            }
        }
       
        @Override
        public String getSortString() {
            return getEntity().getName().getUnqualifiedName();
        }
       
        @Override
        public TypeExpr getTypeExpr() {
            return getEntity().getTypeExpr();
        }
       
        @Override
        public TypeExpr getResultType() {
            return getEntity().getTypeExpr().getResultType();
        }
       
        @Override
        public TypeExpr[] getTypePiecesForBurning() {
            return getEntity().getTypeExpr().getTypePieces();
        }

        @Override
        public TypeExpr[] getArgumentTypePieces() {

            TypeExpr typeExpr = getEntity().getTypeExpr();
            int numArgs = typeExpr.getArity();

            if (numArgs == 0) {
                return null;
               
            } else {
                TypeExpr arguments[] = new TypeExpr[numArgs];
                TypeExpr pieces[] = typeExpr.getTypePieces();

                System.arraycopy(pieces, 0, arguments, 0, numArgs);
               
                return arguments;
            }
        }
       
        @Override
        public boolean hasDisconnectedInputs() {
            return getEntity().getTypeArity() > 0;
        }
       
        @Override
        public boolean isAutoBurnable() {
            return true;
        }
       
        void setNamingPolicy(ScopedEntityNamingPolicy namingPolicy) {
            this.namingPolicy = namingPolicy;
        }
    }
   
    /**
     * A list entry implementation for a TypeExpr.
     * @author Frank Worsley
     */
    private static class TypeExprListEntry extends IntellicutListEntry {
       
        /** The naming policy to use when displaying the entity name. */
        private final ScopedEntityNamingPolicy namingPolicy;
       
        public TypeExprListEntry(TypeExpr typeExpr, ScopedEntityNamingPolicy namingPolicy) {
            super(typeExpr);
           
            if (namingPolicy == null) {
                throw new NullPointerException();
            }
           
            this.namingPolicy = namingPolicy;
        }
       
        @Override
        public String getDisplayString() {
           
            // Special case: Record polymorphic types are displayed as {Record} rather than {r}
            if (getTypeExpr().sameType(TypeExpr.makeFreeRecordType(Collections.<FieldName>emptySet()))) {
                return "{Record}";
               
            } else {
                return getTypeExpr().toString(namingPolicy);
            }
        }
       
        @Override
        public String getSortString() {
            return getTypeExpr().toString(namingPolicy);
        }
       
        @Override
        public TypeExpr getTypeExpr() {
            return (TypeExpr) getData();
        }
       
        @Override
        public TypeExpr getResultType() {
            return getTypeExpr().getResultType();
        }
       
        @Override
        public TypeExpr[] getTypePiecesForBurning() {
            return getTypeExpr().getTypePieces();
        }

        @Override
        public TypeExpr[] getArgumentTypePieces() {

            int numArgs = getTypeExpr().getArity();

            if (numArgs == 0) {
                return null;
               
            } else {
                TypeExpr arguments[] = new TypeExpr[numArgs];
                TypeExpr pieces[] = getTypeExpr().getTypePieces();

                System.arraycopy(pieces, 0, arguments, 0, numArgs);
               
                return arguments;
            }
        }
       
        @Override
        public boolean hasDisconnectedInputs() {
            return getTypeExpr().getArity() > 0;
        }
       
        @Override
        public boolean isAutoBurnable() {
            return true;
        }
    }
   
    /**
     * A list entry implementation for a CollectorGem.
     * @author Frank Worsley
     */
    private static class CollectorGemListEntry extends IntellicutListEntry {
       
        public CollectorGemListEntry(CollectorGem collector) {
            super(collector);
        }
       
        public CollectorGem getCollector() {
            return (CollectorGem) getData();
        }
       
        @Override
        public String getDisplayString() {
            return getCollector().getUnqualifiedName();
        }
       
        @Override
        public String getSortString() {
            return getCollector().getUnqualifiedName();
        }
       
        @Override
        public TypeExpr getResultType() {
            return getCollector().getCollectingPart().getType();
        }
       
        @Override
        public TypeExpr getTypeExpr() {
            return getCollector().getCollectingPart().getType();
        }
       
        @Override
        public TypeExpr[] getTypePiecesForBurning() {
            return null;
        }

        @Override
        public TypeExpr[] getArgumentTypePieces() {
            return getCollector().getCollectingPart().getType().getTypePieces();
        }
       
        @Override
        public boolean hasDisconnectedInputs() {
            return false;
        }

        @Override
        public boolean isAutoBurnable() {
            return false;
        }
    }
   
    /**
     * An adapter that includes all visible gems from the perspective for that can
     * match given input AND output types.
     * @author Frank Worsley
     */
    public static class InputOutputAdapter extends IntellicutListModelAdapter {
       
        /** The type check info to use for autoburning. */
        private final TypeCheckInfo typeCheckInfo;

        /** The workspace to load modules from. */
        private final CALWorkspace workspace;
       
        /** List of valid input types that gems must support. */
        private TypeExpr[] inputExpr = null;
       
        /** List of valid output types that gems must support. */
        private TypeExpr[] outputExpr = null;

        /**
         * Constructor for the InputOuputAdapter which is used by the IntellicutComboBox.
         * 
         * @param inputTypes a list of valid input types to the gems
         * @param outputTypes a list of valid output types to the gems
         * @param workspace the workspace to load gems from
         * @param typeCheckInfo the type check info to use
         */
        public InputOutputAdapter(TypeExpr[] inputTypes,
                                  TypeExpr[] outputTypes,
                                  CALWorkspace workspace,
                                  TypeCheckInfo typeCheckInfo) {
           
            if (workspace == null || typeCheckInfo == null) {
                throw new NullPointerException();
            }
           
            this.inputExpr = inputTypes.clone();
            this.outputExpr = outputTypes.clone();
            this.typeCheckInfo = typeCheckInfo;
            this.workspace = workspace;
           
            setNamingPolicy(new UnqualifiedUnlessAmbiguous(typeCheckInfo.getModuleTypeInfo()));
        }

        @Override
        protected Set<GemEntity> getDataObjects() {
           
            MetaModule targetMetaModule = workspace.getMetaModule(typeCheckInfo.getModuleName());
           
            GemViewer gemViewer = new GemViewer();
            gemViewer.addFilter(new UnorderedTypeMatchFilter(inputExpr, outputExpr, targetMetaModule.getTypeInfo(), true));

            Perspective perspective = new Perspective(workspace, targetMetaModule);
            perspective.setGemEntityViewer(gemViewer);
           
            return getVisibleGemsFromPerspective(perspective);  
        }
       
        @Override
        protected IntellicutInfo getIntellicutInfo(IntellicutListEntry listEntry) {

            // If only output type restrictions have been specified, then calculate type
            // type closeness of the results.
            if (inputExpr == null && outputExpr != null && outputExpr.length > 0) {
                return getIntellicutInfo(listEntry, outputExpr, typeCheckInfo, true, workspace.getSourceMetrics());
            } else {
                return new IntellicutInfo(AutoburnUnifyStatus.NOT_NECESSARY, -1, 1, false, getReferenceFrequency(workspace.getSourceMetrics(), listEntry));
            }
        }

        /**
         * Resets this adapter by clearing all loaded gems and updating the input/output
         * types to use. Next time the model that uses this adapter is refreshed, it will
         * cause the adapter to reload the model.
         * @param inputExpr a list of valid input types to the gems
         * @param outputExpr a list of valid output types from the gems
         */
        public void reset(TypeExpr[] inputExpr, TypeExpr[] outputExpr) {
            this.inputExpr = inputExpr.clone();
            this.outputExpr = outputExpr.clone();
            clear();
        }
    }
   
    /**
     * The adapter used by the IntellicutList for the TableTop. It includes all entities that
     * can connect to a given part connectable.
     * @author Frank Worsley
     */
    public static class PartConnectableAdapter extends IntellicutListModelAdapter {
       
        /** The intellicut part we are building the list for. */
        private final Gem.PartConnectable intellicutPart;
       
        /** The type check info to use for autoburning. */
        private final TypeCheckInfo typeCheckInfo;
       
        /** The perspective we loaded the gems from. */
        private final Perspective perspective;
       
        /**
         * Constructor for IntellicutListModel used by the IntellicutList. The PartConnectable passed in
         * will be used to find a list of gems that can connect to that part.
         *
         * If the connectable is a PartInput then all gems whose output (maybe with burning) can connect to
         * that input will be included. If the connectable is a PartOutput then all gems whose input can
         * connect with the connectable will be included. If the connectable is null then all gems will be
         * included in the list.
         *
         * @param intellicutPart the PartConnectable to match gems with
         * @param perspective the perspective to get gems from
         * @param typeCheckInfo the type check info to use for autoburning
         */
        public PartConnectableAdapter(Gem.PartConnectable intellicutPart,
                                      Perspective perspective,
                                      TypeCheckInfo typeCheckInfo) {

            if (perspective == null || typeCheckInfo == null) {
                throw new NullPointerException();
            }
           
            this.perspective = perspective;
            this.intellicutPart = intellicutPart;
            this.typeCheckInfo = typeCheckInfo;
           
            setNamingPolicy(new UnqualifiedUnlessAmbiguous(perspective.getWorkingModuleTypeInfo()));
        }

        @Override
        protected Set<GemEntity> getDataObjects() {
            return getVisibleGemsFromPerspective(perspective);  
        }
       
        @Override
        protected IntellicutInfo getIntellicutInfo(IntellicutListEntry listEntry) {
           
            if (intellicutPart instanceof Gem.PartInput) {
                return getIntellicutInfo(listEntry, intellicutPart.getType(), typeCheckInfo, true, perspective.getWorkspace().getSourceMetrics());
               
            } else if (intellicutPart instanceof Gem.PartOutput) {
                return getIntellicutInfo(listEntry, intellicutPart.getType(), typeCheckInfo.getModuleTypeInfo(), perspective.getWorkspace().getSourceMetrics());
               
            } else {
                return new IntellicutInfo(AutoburnUnifyStatus.NOT_NECESSARY, -1, 1, false, getReferenceFrequency(perspective.getWorkspace().getSourceMetrics(), listEntry));
            }
        }
       
        /**
         * Resolves ambiguity between displayed names of two entities.
         * Clashes may occur between the displayed unqualified names of
         * Collector and Gem, or Gem and Gem entries; these will be resolved
         * by fully qualifying the names of Gem entries.
         *
         * @see org.openquark.gems.client.IntellicutListModelAdapter#resolveDisplayAmbiguity(org.openquark.gems.client.IntellicutListModelAdapter.IntellicutListEntry, org.openquark.gems.client.IntellicutListModelAdapter.IntellicutListEntry)
         */
        @Override
        protected void resolveDisplayAmbiguity(IntellicutListEntry newEntry, IntellicutListEntry existingEntry) {
            if (newEntry instanceof CollectorGemListEntry) {
                if (existingEntry instanceof GemEntityListEntry) {
                    // Collector clashes with an existing entity
                   
                    // Collector always has unqualified name as its displayed name,
                    // thus the existing entry is fully qualifiable
                    ((GemEntityListEntry)existingEntry).setNamingPolicy(ScopedEntityNamingPolicy.FULLY_QUALIFIED);
                }
               
            } else if (newEntry instanceof GemEntityListEntry) {
                if (existingEntry instanceof GemEntityListEntry) {
                    // Gem vs Gem
                    // Qualify both parties if they are not qualified
                    GemEntityListEntry existingGemEntry = ((GemEntityListEntry)existingEntry);
                    if (!QualifiedName.isValidCompoundName(existingGemEntry.getDisplayString())) {
                        existingGemEntry.setNamingPolicy(ScopedEntityNamingPolicy.FULLY_QUALIFIED);
                        ((GemEntityListEntry)newEntry).setNamingPolicy(ScopedEntityNamingPolicy.FULLY_QUALIFIED);
                    }
                   
                } else if (existingEntry instanceof CollectorGemListEntry) {
                    // Gem vs Collector
                    // Collector always has unqualified name, so qualify the gem
                    ((GemEntityListEntry)newEntry).setNamingPolicy(ScopedEntityNamingPolicy.FULLY_QUALIFIED);
                }
            }
        }
       
        /**
         * Filters gems based on reference frequency and type closeness if possible.
         * If there is no type to compare for type closeness, then uses the inherited
         * reference-frequency-only filter.
         *
         * @param listEntry The entry to check against the specified filter
         * @param filterLevel The level of filter to apply
         * @return true if the specified list entry passes the specified filter
         */
        @Override
        public boolean passesFilter(IntellicutListEntry listEntry, FilterLevel filterLevel) {
           
            if (filterLevel == null) {
                throw new NullPointerException("filterLevel must not be null in IntellicutListModelAdapter.PartConnectableAdapter.passesFilter");
            }
       
            // If we have no current part (and therefore can't do type closeness checks),
            // then defer to the default filtering (which only uses reference frequency)
            if (intellicutPart == null) {
                return super.passesFilter(listEntry, filterLevel);
            }
       
            // Filter gems based on both reference frequency and type closeness
            if (filterLevel == FilterLevel.SHOW_BEST) {
                if (listEntry.getIntellicutInfo().isSameNonpolymorphicType()) {
                    return listEntry.getIntellicutInfo().isSameNonpolymorphicTypeThreshold() ||
                           listEntry.getIntellicutInfo().isReferenceFrequencyInTopFifth();
                } else {
                    return (listEntry.getIntellicutInfo().getMaxTypeCloseness() >= 1) &&
                           (listEntry.getIntellicutInfo().isReferenceFrequencyInTopFifth());
                }
            } else if (filterLevel == FilterLevel.SHOW_LIKELY) {
                return (listEntry.getIntellicutInfo().getMaxTypeCloseness() >= 1) ||
                (listEntry.getIntellicutInfo().isReferenceFrequencyInTopFifth());
            } else { // SHOW_ALL
                return true;
            }
        }
    }
   
    /**
     * Gems are divided into those whose reference frequency is in the top
     * REFERENCE_FREQUENCY_PERCENTILE_THRESHOLD percent, and those whose
     * frequency is not.
     */
    private static final int REFERENCE_FREQUENCY_PERCENTILE_THRESHOLD = 20;
   
    /**
     * The top SAME_NONPOLYMORPHIC_TYPE_REFERENCE_FREQUENCY_RANK gems with the
     * same non-polymorphic type as the target gem are included in the Best Gems
     * list even if they are not in the top REFERENCE_FREQUENCY_PERCENTILE_THRESHOLD
     * of gems by reference frequency.  (See IntellicutInfo for a description of why).
     */
    private static final int SAME_NONPOLYMORPHIC_TYPE_REFERENCE_FREQUENCY_RANK = 10;
   
    /**
     * This set contains all the list entries that can be shown in the intellicut list.
     * That is the combination of entries for the entities from the perspective and entries
     * for the additional gems that have been added. Not all entries in this set may be
     * visible at all times, this depends on the show all/show best gems setting and the
     * prefix that the user has typed in.
     */
    private final SortedSet<IntellicutListEntry> intellicutEntrySet = new TreeSet<IntellicutListEntry>(new IntellicutListEntryComparator());
     
    /**
     * A map from a data object to the associated IntellicutListEntry.
     */
    private final Map<Object, IntellicutListEntry> intellicutEntryMap = new HashMap <Object,IntellicutListEntry>();
   
    /**
     * A set of additional objects that should be added to the model as list entries.
     */
    private final Set<Gem> additionalDataObjects = new HashSet<Gem>();
   
    /** The visibility policy to use to decide which items to display. */
    private FeatureVisibilityPolicy visibilityPolicy = FeatureVisibilityPolicy.getDefault();
   
    /** The naming policy to use to display scoped entity names. */
    private ScopedEntityNamingPolicy namingPolicy = ScopedEntityNamingPolicy.FULLY_QUALIFIED;
   
    /**
     * Returns the set of all available data objects that will be used to create
     * list items. Subclasses override this to provide whatever set of objects
     * they want. Note that a corresponding list entry type for the data type must
     * exist, otherwise an exception will be thrown when the list entries are created.
     * @return Set of Object
     */
    protected abstract Set<?> getDataObjects();
   
    /**
     * Returns new IntellicutInfo for a given list entry. If the entry should
     * not appear in the list, then this method should return null. Subclasses override
     * this to not create list items for entities they do not want to appear.
     * @param listEntry the list entry for which to get intellicut info
     * @return the intellicut info for the given list entry
     */
    protected abstract IntellicutInfo getIntellicutInfo(IntellicutListEntry listEntry);
   
    /**
     * Returns a new IntellicutListEntry for a data object. The default implementation
     * supports GemEntity, CollectorGem and TypeExpr data object types. Subclasses override
     * this if they want to add support for additional data object types.
     * @param dataObject the data object to get a list entry for
     * @return the list entry for the data object. This method should never return null.
     * @throws IllegalArgumentException if the data object type is not supported
     */
    protected IntellicutListEntry getNewListEntry(Object dataObject) {
       
        if (dataObject instanceof GemEntity) {
            return new GemEntityListEntry((GemEntity) dataObject, namingPolicy);
           
        } else if (dataObject instanceof CollectorGem) {
            return new CollectorGemListEntry((CollectorGem) dataObject);
           
        } else if (dataObject instanceof TypeExpr) {
            return new TypeExprListEntry((TypeExpr) dataObject, namingPolicy);
        }
       
        throw new IllegalArgumentException("data object not supported: " + dataObject);
    }
   
    /**
     * Filters gems based on reference frequency only.  Gems whose reference frequency
     * are in the top fifth are both Likely and Best.
     *
     * @param listEntry the list entry to check
     * @param filterLevel the filter level to use
     * @return whether or not the given list entry passes the specified filter
     */
    public boolean passesFilter(IntellicutListEntry listEntry, FilterLevel filterLevel) {

        if (filterLevel == null) {
            throw new NullPointerException("filterLevel must not be null in IntellicutListModelAdapter.passesFilter");
        }

        // Filter based on reference frequency
        if (filterLevel == FilterLevel.SHOW_ALL) {
            return true;
        } else {
            return listEntry.getIntellicutInfo().isReferenceFrequencyInTopFifth();
        }
    }
   
    /**
     * @param namingPolicy the naming policy to use to display scoped entity names
     */
    public void setNamingPolicy(ScopedEntityNamingPolicy namingPolicy) {
        this.namingPolicy = namingPolicy;
    }
   
    /**
     * @return the naming policy used by this adapter to display scoped entity names
     */
    public ScopedEntityNamingPolicy getNamingPolicy() {
        return namingPolicy;
    }
   
    /**
     * @param visibilityPolicy the visibility policy to use to decide what items to display
     */
    public void setVisibilityPolicy(FeatureVisibilityPolicy visibilityPolicy) {
        this.visibilityPolicy = visibilityPolicy;
    }
   
    /**
     * @return the visibility policy used by this adapter to decide what items to display
     */
    public FeatureVisibilityPolicy getVisibilityPolicy() {
        return visibilityPolicy;
    }
   
    /**
     * Includes the set of specified object in the list of data object to be loaded by the model.
     * This must be called before the model is loaded the first time.
     * @param additionalDataObjects a set of Gems or GemEntities to be added
     */
    public void addAdditionalDataObjects(Set<? extends Gem> additionalDataObjects) {
        this.additionalDataObjects.addAll(additionalDataObjects);
    }
   
    /**
     * Clears all entries that have been loaded. Calling this will cause the adapter
     * to reload entries the next time the model that uses it is refreshed.
     */
    public void clear() {
        intellicutEntrySet.clear();
        intellicutEntryMap.clear();
    }
   
    /**
     * Performs the actual loading of the list items.
     */
    public void load() {
        // Clear previously loaded stuff
        clear();
       
        // Load entries for entities from the perspective.
        loadListEntries(getDataObjects());
        loadListEntries(additionalDataObjects);
    }
   
    /**
     * Loads new list entries from a given data set and puts them into the result collection.
     * This method also resolves ambiguities between entities with same displayed string.
     * @param sourceDataSet the source data set from which to create them
     */       
    private void loadListEntries(Set<?> sourceDataSet) {
   
        SortedSet<IntellicutListEntry> additionalEntrySet = new TreeSet<IntellicutListEntry>(intellicutEntrySet.comparator());
        int[] referenceFrequencies = new int[sourceDataSet.size()];
        int[] sameNonpolymorphicTypeReferenceFrequencies = new int[sourceDataSet.size()];
        Map<Object, IntellicutListEntry> additionalEntryMap = new HashMap<Object, IntellicutListEntry>();
       
        // First gather a list of all the IntellicutInfo objects, keeping track
        // of the reference frequencies of each in referenceFrequencies (so that
        // we can sort them later)
        Arrays.fill(referenceFrequencies, 0);
        Arrays.fill(sameNonpolymorphicTypeReferenceFrequencies, 0);
        int numReferenceFrequencies = 0;
        int numSameNonpolymorphicType = 0;
   
        for (final Object dataObject : sourceDataSet) {
            IntellicutListEntry listEntry = getNewListEntry(dataObject);
            IntellicutInfo info = getIntellicutInfo(listEntry);
            if (info != null) {
                listEntry.setIntellicutInfo(info);
               
                additionalEntrySet.add(listEntry);
                additionalEntryMap.put(listEntry.getData(), listEntry);
               
                referenceFrequencies[numReferenceFrequencies] = info.getReferenceFrequency();
                numReferenceFrequencies++;
               
                if (info.isSameNonpolymorphicType()) {
                    sameNonpolymorphicTypeReferenceFrequencies[numSameNonpolymorphicType] = info.getReferenceFrequency();
                    numSameNonpolymorphicType++;
                }
            }
        }
       
        // Sort reference frequencies to determine the 80th percentile
        Arrays.sort(referenceFrequencies);
        int referenceFrequencyThresholdIdx = (referenceFrequencies.length-1) - (numReferenceFrequencies/(100 / REFERENCE_FREQUENCY_PERCENTILE_THRESHOLD));
       
        // Sort reference frequencies of the gems with the same nonpolymorphic type to
        // determine the threshold for the top 10 values (if there are more than 10 values)
        int sameNonpolymorphicTypeReferenceFrequencyThreshold = 0;
        if (numSameNonpolymorphicType > SAME_NONPOLYMORPHIC_TYPE_REFERENCE_FREQUENCY_RANK) {
            Arrays.sort(sameNonpolymorphicTypeReferenceFrequencies);
            sameNonpolymorphicTypeReferenceFrequencyThreshold = sameNonpolymorphicTypeReferenceFrequencies[(sameNonpolymorphicTypeReferenceFrequencies.length - 1) - 10];
        }
       
        // Step through again to normalize the heuristic outputs
        for (final IntellicutListEntry listEntry : additionalEntrySet) {
            listEntry.getIntellicutInfo().setTopFifthThresholds(referenceFrequencies[referenceFrequencyThresholdIdx]);
            listEntry.getIntellicutInfo().setSameNonpolymorphicTypeReferenceFrequencyThreshold(sameNonpolymorphicTypeReferenceFrequencyThreshold);
        }
       
        if (intellicutEntrySet.isEmpty()) {
            intellicutEntrySet.addAll(additionalEntrySet);
            return;
        }
       
        // Entries in the original set may be modified, and this set tracks them
        SortedSet<IntellicutListEntry> modifiedOriginalEntries = new TreeSet<IntellicutListEntry>(intellicutEntrySet.comparator());
       
        // Lazily iterate through the existing entry set, while progressing
        // through the new entries.
        Iterator<IntellicutListEntry> o = intellicutEntrySet.iterator();
        IntellicutListEntry originalEntry = o.next();
        for (final IntellicutListEntry newEntry : additionalEntrySet) {
            // Hop over original entries until we reach our a sort string equal/greater
            // than that of the new entry's.
            while (o.hasNext() && newEntry.getSortString().compareToIgnoreCase(originalEntry.getSortString()) > 0) {
                originalEntry = o.next();
            }
           
            // If sort strings are equal, then there is a possibility the display strings are equal
            // Note: Displayed strings will not be equal if sort strings are different.
            if ((newEntry.getSortString().compareToIgnoreCase(originalEntry.getSortString()) == 0) &&
                (newEntry.getDisplayString().compareToIgnoreCase(originalEntry.getDisplayString()) == 0)) {
               
                resolveDisplayAmbiguity(newEntry, originalEntry);
                modifiedOriginalEntries.add(originalEntry);
            }
        }
       
        // Refresh the modified entry in the original set
        intellicutEntrySet.removeAll(modifiedOriginalEntries);
        intellicutEntrySet.addAll(modifiedOriginalEntries);
       
        // Now add the new entries to this set
        intellicutEntrySet.addAll(additionalEntrySet);
    }
   
    /**
     * Resolves an ambiguity in the displayed string between two list entities.
     *
     * This method is invoked when an entity must be added to the intellicut list,
     * but its display string equals that of another entity. The purpose of this
     * method is to modify one or both of the entities in order to resolve
     * this ambiguity. It is assumed that the resolution does not cause extra
     * ambiguities.
     *
     * If resolution is not possible, the new entry will be added to the list
     * entries as is, and the list will contain two entries with identical
     * display strings.
     *
     * Note: The ambiguity finding mechanism assumes that it is not possible for
     * resolution to cause further ambiguities, as these would not be detected.
     *
     * @param newEntry entry which is to be added to the list
     * @param existingEntry entry already existing in the list
     */
    protected void resolveDisplayAmbiguity(IntellicutListEntry newEntry, IntellicutListEntry existingEntry) {
       
        // By default, no action is taken to resolve the ambiguity
        return;
    }
   
    /**
     * @return the set of all loaded list entries sorted in alphabetical order
     */
    public SortedSet<IntellicutListEntry> getListEntrySet() {
        return Collections.unmodifiableSortedSet(intellicutEntrySet);
    }
   
    /**
     * @param data the data object to get a list entry for
     * @return the list entry that corresponds to the given data object or null if no such entry
     */
    public IntellicutListEntry getListEntryForData(Object data) {
        return intellicutEntryMap.get(data);
    }
   
    /**
     * @param listEntry the list entry to get the tooltip text for
     * @param invoker the component to get the tooltip text for
     * @return the tooltip text for the list entry
     */
    public String getToolTipTextForEntry(IntellicutListEntry listEntry, JComponent invoker) {
       
        Object data = listEntry.getData();
       
        if (data instanceof GemEntity) {
            return ToolTipHelpers.getEntityToolTip(((GemEntity)data), namingPolicy, invoker);
        }
           
        return listEntry.getDisplayString();
    }

    /**
     * Returns a set of gems that visible to the user.  The visibility of a gem is determined
     * by several checks.  First, the module containing the gem has to be visible from the
     * perspective.  For example, the module has to be referenced by the current working module.
     * Second, the module has to go through the visibility predicate check to ensure its
     * visibility.  The third check is to ensure that the given gem is a public gem, or it
     * is part of the working module.
     * @param perspective
     */
    protected Set<GemEntity> getVisibleGemsFromPerspective(Perspective perspective) {
       
        Set<GemEntity> entities = new LinkedHashSet<GemEntity>();
        List<MetaModule> modules = perspective.getVisibleMetaModules();
       
        for (int i = 0, size = modules.size(); i < size; i++) {
           
            MetaModule module = modules.get(i);
           
            if (visibilityPolicy.isModuleVisible(module)) {
                // TODO currently we don't support metadata expert/hidden/preferred flag
                // checking for gem entities, so as long as the module is visible,
                // all public gems are visible
                entities.addAll(perspective.getVisibleGemEntities(module));
            }
        }
       
        return entities;
    }
   
    /**
     * Returns a new IntellicutInfo if the output type of the given entry data can be connected
     * to the specified type expression. Returns null if the connection is not possible.
     * @param data the IntellicutListEntry for the new entry
     * @param destTypeExpr the type that the data must connect to
     * @param typeCheckInfo the type check info to use
     * @param sourceMetrics The WorkspaceSourceMetrics provider to fetch reference frequency with.    
     * @return new IntellicutInfo or null if connection not possible
     */
    protected static IntellicutInfo getIntellicutInfo(IntellicutListEntry data, TypeExpr destTypeExpr, TypeCheckInfo typeCheckInfo, boolean allowFreeArguments, SourceMetrics sourceMetrics) {
        return getIntellicutInfo(data, new TypeExpr[] { destTypeExpr }, typeCheckInfo, allowFreeArguments, sourceMetrics);
    }

    /**
     * Get the reference frequency for a specific list entry
     * @param sourceMetrics the metrics store to fetch the frequency from
     * @param candidate the list entry to fetch the frequency for
     * @return The number of times the specified gem is referred to in the body of other gems
     */
    protected static int getReferenceFrequency(SourceMetrics sourceMetrics, IntellicutListEntry candidate) {
        if (sourceMetrics != null && candidate instanceof GemEntityListEntry) {
           
            GemEntityListEntry gemCandidate = (GemEntityListEntry)candidate;
            QualifiedName name = gemCandidate.getEntity().getName();
            return sourceMetrics.getGemReferenceFrequency(name);
        }
        return 0;
    }
   
    /**
     * Returns a new IntellicutListEntry if the output type of the given entry data can be connected
     * to at least one of the specified type expression. Returns null if the connection is not possible.
     * @param outputListEntry the IntellicutListEntry
     * @param destTypeExprs the types available for the data to connect to
     * @param typeCheckInfo the type check info to use  
     * @param allowFreeArguments whether or not the entry data is allowed to have free arguments
     * @param sourceMetrics The WorkspaceSourceMetrics provider to fetch reference frequency with.    
     * @return new IntellicutListEntry or null if connection not possible
     */
    protected static IntellicutInfo getIntellicutInfo(IntellicutListEntry outputListEntry, TypeExpr[] destTypeExprs, TypeCheckInfo typeCheckInfo, boolean allowFreeArguments, SourceMetrics sourceMetrics) {
        TypeExpr outputType = outputListEntry.getResultType();
        int dependents = getReferenceFrequency(sourceMetrics, outputListEntry);
       
        // Code gems return null if no code is entered in the code editor.
        if (outputType == null || destTypeExprs == null) {
            return null;
        }

        ModuleTypeInfo currentModuleTypeInfo = typeCheckInfo.getModuleTypeInfo();

        boolean targetIsNonParametric = !outputType.isPolymorphic();
        boolean exactMatch = false;
       
        // Check whether any of the provided types are an exact match
        for(int typeN = 0, nTypes=destTypeExprs.length; typeN < nTypes; ++typeN) {
            if (targetIsNonParametric && outputType.sameType(destTypeExprs[typeN])) {
                exactMatch = true;
                break;
            }
        }
       
        // Check to see if the destTypeExpr and outputType are 'connectable'.
        if (!outputListEntry.isAutoBurnable()) {
           
            // Try connecting without burning.
            // Find the maximum closeness value for the types provided.
            int noBurnTypeCloseness = -1;
            for (int typeN = 0, nTypes = destTypeExprs.length; typeN < nTypes; ++typeN) {
                int closenessForType = TypeExpr.getTypeCloseness(destTypeExprs[typeN], outputType, currentModuleTypeInfo);
   
                if (closenessForType > noBurnTypeCloseness) {
                    noBurnTypeCloseness = closenessForType;
                }
            }           

            if (noBurnTypeCloseness != -1 && (allowFreeArguments || !outputListEntry.hasDisconnectedInputs())) {
                return new IntellicutInfo(AutoburnUnifyStatus.NOT_NECESSARY, -1, noBurnTypeCloseness, exactMatch, dependents);
            }
           
            return null;

        }
       
        // Try connecting with auto burning.
        // Find the autoburning for the type which provides the maximum closeness value.
        AutoburnLogic.AutoburnInfo autoburnInfo = null;
       
        int noBurnTypeCloseness = -1;

        for (int typeN = 0, nTypes = destTypeExprs.length; typeN < nTypes; ++typeN) {
            AutoburnLogic.AutoburnInfo autoburnInfoForType = AutoburnLogic.getAutoburnInfo(destTypeExprs[typeN], outputListEntry.getTypePiecesForBurning(), typeCheckInfo);
           
            noBurnTypeCloseness = Math.max(noBurnTypeCloseness, autoburnInfoForType.getNoBurnTypeCloseness());
           
            if (autoburnInfo == null || autoburnInfoForType.getMaxTypeCloseness() > autoburnInfo.getMaxTypeCloseness()) {
                autoburnInfo = autoburnInfoForType;
            }
        }

        if (autoburnInfo != null) {
            AutoburnUnifyStatus autoburnUnifyStatus = autoburnInfo.getAutoburnUnifyStatus();
           
            if (autoburnUnifyStatus != AutoburnUnifyStatus.NOT_POSSIBLE &&
                    (allowFreeArguments || canBurnAllArguments(outputListEntry, autoburnInfo))) {
               
                return new IntellicutInfo(autoburnInfo.getAutoburnUnifyStatus(), autoburnInfo.getMaxTypeCloseness(), noBurnTypeCloseness, exactMatch, dependents);
            }
        }       
       
        return null;
    }

    /**
     * Returns a new IntellicutListEntry if any of the input types of the given list entry data
     * can be connected to the specified type expression. Returns null if the connection is not possible.
     * @param inputListEntry the IntellicutListEntry object
     * @param sourceTypeExpr the type that the entity must connect to
     * @param moduleTypeInfo the type info of the context module
     * @return new IntellicutListEntry or null if connection not possible
     */
    protected static IntellicutInfo getIntellicutInfo(IntellicutListEntry inputListEntry, TypeExpr sourceTypeExpr, ModuleTypeInfo moduleTypeInfo, SourceMetrics sourceMetrics) {

        TypeExpr[] typePieces = inputListEntry.getArgumentTypePieces();

        // No arguments means we can't connect to it.
        if (typePieces == null) {
            return null;
        }
       
        int references = getReferenceFrequency(sourceMetrics, inputListEntry);

        boolean foundCandidate = false;
        int finalClosenessForType = 0;
        boolean finalExactMatch = false;
        boolean sourceIsNonParametric = !sourceTypeExpr.isPolymorphic();
       
        for (final TypeExpr typeExp : typePieces) {
           
            // Check to see if the sourceTypeExpr and one of the type pieces are 'connectable'.
            // We need to check the whole list, because we might encounter a "connectable but not exact"
            // piece before we encounter a "connectable exact match" piece.
            if (TypeExpr.canUnifyType(sourceTypeExpr, typeExp, moduleTypeInfo)) {
               
                foundCandidate = true;
               
                int closenessForType = TypeExpr.getTypeCloseness(sourceTypeExpr, typeExp, moduleTypeInfo);
                boolean exactMatch = sourceIsNonParametric && sourceTypeExpr.sameType(typeExp);
               
                if (exactMatch || closenessForType > finalClosenessForType) {
                    finalClosenessForType = closenessForType;
                    finalExactMatch = exactMatch || finalExactMatch;
                }
            }
        }              

        if (foundCandidate) {
            return new IntellicutInfo(AutoburnUnifyStatus.NOT_NECESSARY, -1, finalClosenessForType, finalExactMatch, references);
        } else {
            return null;
        }
    }
   
    /**
     * Checks if the given autoburn info indicates that all arguments of the given list entry data can be burned.
     * @param listEntry the list entry
     * @param autoburnInfo the autoburn info
     * @return true if all arguments can be burned
     */
    private static boolean canBurnAllArguments(IntellicutListEntry listEntry, AutoburnLogic.AutoburnInfo autoburnInfo) {
       
        List<BurnCombination> burnCombos = autoburnInfo.getBurnCombinations();
       
        if (burnCombos.isEmpty()) {
            return false;
        }
       
        AutoburnLogic.BurnCombination lastBurnCombo = burnCombos.get(burnCombos.size() - 1);

        return lastBurnCombo.getInputsToBurn().length == listEntry.getTypePiecesForBurning().length - 1;
    }
}
TOP

Related Classes of org.openquark.gems.client.IntellicutListModelAdapter$IntellicutListEntry

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.