Package org.eclipse.php.internal.ui.editor.contentassist

Source Code of org.eclipse.php.internal.ui.editor.contentassist.ParameterGuessingProposal

/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*    Andrew McCullough - initial API and implementation
*    IBM Corporation  - general improvement and bug fixes, partial reimplementation
*******************************************************************************/
package org.eclipse.php.internal.ui.editor.contentassist;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Preferences;
import org.eclipse.dltk.core.*;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.*;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.link.*;
import org.eclipse.php.core.compiler.PHPFlags;
import org.eclipse.php.internal.core.PHPCoreConstants;
import org.eclipse.php.internal.core.PHPCorePlugin;
import org.eclipse.php.internal.core.PHPVersion;
import org.eclipse.php.internal.core.codeassist.AliasType;
import org.eclipse.php.internal.core.codeassist.ProposalExtraInfo;
import org.eclipse.php.internal.core.compiler.ast.nodes.NamespaceReference;
import org.eclipse.php.internal.core.project.ProjectOptions;
import org.eclipse.php.internal.core.typeinference.FakeConstructor;
import org.eclipse.php.internal.core.typeinference.PHPModelUtils;
import org.eclipse.php.internal.ui.PHPUiPlugin;
import org.eclipse.php.internal.ui.text.template.contentassist.PositionBasedCompletionProposal;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;

/**
* This is a
* {@link org.eclipse.jdt.internal.ui.text.java.JavaCompletionProposal} which
* includes templates that represent the best guess completion for each
* parameter of a method.
*/
public final class ParameterGuessingProposal extends
    PHPOverrideCompletionProposal implements
    IPHPCompletionProposalExtension {
  private static final String PARENS = "()"; //$NON-NLS-1$
  private static final char[] NO_TRIGGERS = new char[0];
  protected static final String LPAREN = "("; //$NON-NLS-1$
  protected static final String RPAREN = ")"; //$NON-NLS-1$
  protected static final String COMMA = ", "; //$NON-NLS-1$
  protected static final String SPACE = " "; //$NON-NLS-1$
  private CompletionProposal fProposal;
  private IMethod method;
  private final boolean fFillBestGuess;
  private boolean fReplacementStringComputed = false;
  private Object extraInfo;
  private boolean fReplacementLengthComputed;
  private String alias = null;
  private IDocument document = null;
  private IScriptProject sProject = null;

  public ParameterGuessingProposal(CompletionProposal proposal,
      IScriptProject jproject, ISourceModule cu, String methodName,
      String[] paramTypes, int start, int length, String displayName,
      String completionProposal, boolean fillBestGuess, Object extraInfo,
      IDocument document) {
    super(jproject, cu, methodName, paramTypes, start, length, displayName,
        completionProposal);
    this.fProposal = proposal;
    method = (IMethod) fProposal.getModelElement();
    this.fFillBestGuess = fillBestGuess;
    this.extraInfo = extraInfo;
    this.document = document;
    this.sProject = jproject;
  }

  /**
   * if modelElement is an instance of FakeConstructor, we need to get the
   * real constructor
   *
   * @param modelElement
   * @return
   */
  private IMethod getProperMethod(IMethod modelElement) {
    if (modelElement instanceof FakeConstructor) {
      FakeConstructor fc = (FakeConstructor) modelElement;
      if (fc.getParent() instanceof AliasType) {
        AliasType aliasType = (AliasType) fc.getParent();
        alias = aliasType.getAlias();
        if (aliasType.getParent() instanceof IType) {
          fc = FakeConstructor.createFakeConstructor(null,
              (IType) aliasType.getParent(), false);
        }
      }
      IType type = fc.getDeclaringType();
      IMethod[] ctors = FakeConstructor.getConstructors(type,
          fc.isEnclosingClass());
      // here we must make sure ctors[1] != null,
      // it means there is an available FakeConstructor for ctors[0]
      if (ctors != null && ctors.length == 2 && ctors[0] != null
          && ctors[1] != null) {
        return ctors[0];
      }
      return fc;
    }

    return modelElement;
  }

  private ICompletionProposal[][] fChoices; // initialized by
  // guessParameters()
  private Position[] fPositions; // initialized by guessParameters()

  private IRegion fSelectedRegion; // initialized by apply()
  private IPositionUpdater fUpdater;

  /*
   * @see ICompletionProposalExtension#apply(IDocument, char)
   */
  public void apply(IDocument document, char trigger, int offset) {
    try {
      dealSuffix(document, offset);
      super.apply(document, trigger, offset);

      int baseOffset = getReplacementOffset();
      String replacement = getReplacementString();

      if (fPositions != null && fPositions.length > 0
          && getTextViewer() != null) {

        LinkedModeModel model = new LinkedModeModel();

        for (int i = 0; i < fPositions.length; i++) {
          LinkedPositionGroup group = new LinkedPositionGroup();
          int positionOffset = fPositions[i].getOffset();
          int positionLength = fPositions[i].getLength();

          if (fChoices[i].length < 2) {
            group.addPosition(new LinkedPosition(document,
                positionOffset, positionLength,
                LinkedPositionGroup.NO_STOP));
          } else {
            ensurePositionCategoryInstalled(document, model);
            document.addPosition(getCategory(), fPositions[i]);
            group.addPosition(new ProposalPosition(document,
                positionOffset, positionLength,
                LinkedPositionGroup.NO_STOP, fChoices[i]));
          }
          model.addGroup(group);
        }

        model.forceInstall();

        LinkedModeUI ui = new EditorLinkedModeUI(model, getTextViewer());
        ui.setExitPosition(getTextViewer(),
            baseOffset + replacement.length(), 0, Integer.MAX_VALUE);
        // ui.setExitPolicy(new ExitPolicy(')', document));
        ui.setCyclingMode(LinkedModeUI.CYCLE_WHEN_NO_PARENT);
        ui.setDoContextInfo(true);
        ui.enter();
        fSelectedRegion = ui.getSelectedRegion();

      } else {
        fSelectedRegion = new Region(baseOffset + getCursorPosition(),
            0);
      }

    } catch (BadLocationException e) {
      ensurePositionCategoryRemoved(document);
      PHPUiPlugin.log(e);
      openErrorDialog(e);
    } catch (BadPositionCategoryException e) {
      ensurePositionCategoryRemoved(document);
      PHPUiPlugin.log(e);
      openErrorDialog(e);
    }
  }

  private void dealSuffix(IDocument document, int offset) {
    boolean toggleEating = isToggleEating();
    boolean instertCompletion = insertCompletion();
    // https://bugs.eclipse.org/bugs/show_bug.cgi?id=395654
    // workaround for GlobalTypesStrategy.getReplacementRange()
    if (instertCompletion && toggleEating) {
      setReplacementLength(getReplacementLength() + 1);
    }

    String replacement = getReplacementString();
    if (replacement.endsWith(RPAREN)) {
      if (instertCompletion && toggleEating) {
        if (cursorInBrackets(document, getReplacementOffset()
            + getReplacementLength() + 1)) {
          setReplacementLength(getReplacementLength() + 2);
        }
      } else {
        if (cursorInBrackets(document, offset + 1)) {
          setReplacementLength(getReplacementLength() + 2);
        }
      }
    } else {
      // deal with case that a method that do not have parameters but with
      // append with parameters when using insert mode,for example
      // getA|($a),we should generate getA()($a) instead of getA($a),but
      // for getA|(),we should generate getA()
      if (insertCompletion() && !cursorInBrackets(document, offset + 1)) {
        replacement = replacement + PARENS;
        setReplacementString(replacement);
      }
    }
  }

  private boolean cursorInBrackets(IDocument document, int offset) {
    String nextWord = null;
    try {
      nextWord = document.get(offset - 1, 2);// "()".length()
    } catch (BadLocationException e) {
    }
    return PARENS.equals(nextWord);
  }

  /**
   * Gets the replacement length.
   *
   * @return Returns a int
   */
  public final int getReplacementLength() {
    if (!fReplacementLengthComputed)
      setReplacementLength(fProposal.getReplaceEnd()
          - fProposal.getReplaceStart());
    return super.getReplacementLength();
  }

  /**
   * Sets the replacement length.
   *
   * @param replacementLength
   *            The replacementLength to set
   */
  public final void setReplacementLength(int replacementLength) {
    fReplacementLengthComputed = true;
    super.setReplacementLength(replacementLength);
  }

  /*
   * @seeorg.eclipse.jdt.internal.ui.text.java.JavaMethodCompletionProposal#
   * needsLinkedMode()
   */
  protected boolean needsLinkedMode() {
    return false; // we handle it ourselves
  }

  public String getReplacementString() {
    String prefix = ""; //$NON-NLS-1$
    try {
      if (method.isConstructor()) {
        IType type = method.getDeclaringType();
        try {
          int flags = type.getFlags();
          IType currentNamespace = PHPModelUtils
              .getCurrentNamespaceIfAny(fSourceModule,
                  getReplacementOffset());
          IType namespace = PHPModelUtils.getCurrentNamespace(type);
          if (!PHPFlags.isNamespace(flags)
              && namespace == null
              && currentNamespace != null
              && !ProjectOptions.getPhpVersion(
                  sProject.getProject()).isLessThan(
                  PHPVersion.PHP5_3)
              && document.getChar(getReplacementOffset() - 1) != NamespaceReference.NAMESPACE_SEPARATOR) {
            prefix = prefix
                + NamespaceReference.NAMESPACE_SEPARATOR;
          }
        } catch (ModelException e) {
          PHPUiPlugin.log(e);
        } catch (BadLocationException e) {
          PHPUiPlugin.log(e);
        }
      }
    } catch (ModelException e) {
    }
    if (ProposalExtraInfo.isMethodOnly(extraInfo)) {
      setReplacementString(prefix + method.getElementName());
      return super.getReplacementString();
    }
    boolean fileArgumentNames = Platform.getPreferencesService()
        .getBoolean(PHPCorePlugin.ID,
            PHPCoreConstants.CODEASSIST_FILL_ARGUMENT_NAMES, true,
            null);
    if (fileArgumentNames && !fReplacementStringComputed)
      setReplacementString(prefix + computeReplacementString());
    if (!fileArgumentNames)
      setReplacementString(prefix + super.getReplacementString());

    return super.getReplacementString();
  }

  private String computeReplacementString() {
    fReplacementStringComputed = true;
    try {
      // we should get the real constructor here
      method = getProperMethod(method);
      if (alias != null || hasParameters() && hasArgumentList()) {
        return computeGuessingCompletion();
      }
    } catch (ModelException e) {
      e.printStackTrace();
    }
    return super.getReplacementString();
  }

  /**
   * Returns <code>true</code> if the argument list should be inserted by the
   * proposal, <code>false</code> if not.
   *
   * @return <code>true</code> when the proposal is not in javadoc nor within
   *         an import and comprises the parameter list
   */
  protected boolean hasArgumentList() {
    if (CompletionProposal.METHOD_NAME_REFERENCE == fProposal.getKind())
      return false;
    Preferences preferenceStore = PHPCorePlugin.getDefault()
        .getPluginPreferences();
    boolean noOverwrite = preferenceStore
        .getBoolean(PHPCoreConstants.CODEASSIST_INSERT_COMPLETION)
        ^ isToggleEating();
    char[] completion = fProposal.getCompletion().toCharArray();
    return !isInScriptdoc() && completion.length > 0
        && (noOverwrite || completion[completion.length - 1] == ')');
  }

  protected boolean isValidPrefix(String prefix) {
    initAlias();
    String replacementString = null;
    if (alias != null) {
      replacementString = getAlias();
    } else {
      replacementString = super.getReplacementString();
    }
    return isPrefix(prefix, replacementString);
  }

  public String getAlias() {
    return alias + LPAREN + RPAREN;
  }

  private void initAlias() {
    if (method instanceof FakeConstructor) {
      FakeConstructor fc = (FakeConstructor) method;
      if (fc.getParent() instanceof AliasType) {
        alias = ((AliasType) fc.getParent()).getAlias();
      }
    }
  }

  private boolean hasParameters() throws ModelException {
    return method.getParameters() != null
        && hasNondefaultValues(method.getParameters());
  }

  private boolean hasNondefaultValues(IParameter[] parameters) {
    for (int i = 0; i < parameters.length; i++) {
      IParameter parameter = parameters[i];
      if (parameter.getDefaultValue() == null) {
        return true;
      }
    }
    return false;
  }

  /**
   * Creates the completion string. Offsets and Lengths are set to the offsets
   * and lengths of the parameters.
   *
   * @return the completion string
   * @throws ModelException
   *             if parameter guessing failed
   */
  private String computeGuessingCompletion() throws ModelException {

    StringBuffer buffer = new StringBuffer();
    appendMethodNameReplacement(buffer);

    setCursorPosition(buffer.length());
    // show method parameter names:
    IParameter[] parameters = method.getParameters();
    List<String> paramList = new ArrayList<String>();
    if (parameters != null) {
      for (int i = 0; i < parameters.length; i++) {
        IParameter parameter = parameters[i];
        if (parameter.getDefaultValue() == null) {
          paramList.add(parameter.getName());
        }
      }
    }
    char[][] parameterNames = new char[paramList.size()][];
    for (int i = 0; i < paramList.size(); ++i) {
      parameterNames[i] = paramList.get(i).toCharArray();
    }

    fChoices = guessParameters(parameterNames);
    int count = fChoices.length;
    int replacementOffset = getReplacementOffset();

    for (int i = 0; i < count; i++) {
      if (i != 0) {
        buffer.append(COMMA);
      }

      ICompletionProposal proposal = fChoices[i][0];
      String argument = proposal.getDisplayString();

      Position position = fPositions[i];
      position.setOffset(replacementOffset + buffer.length());
      position.setLength(argument.length());

      buffer.append(argument);
    }

    buffer.append(RPAREN);

    return buffer.toString();
  }

  /**
   * Appends everything up to the method name including the opening
   * parenthesis.
   * <p>
   * In case of {@link CompletionProposal#METHOD_REF_WITH_CASTED_RECEIVER} it
   * add cast.
   * </p>
   *
   * @param buffer
   *            the string buffer
   * @since 3.4
   */
  protected void appendMethodNameReplacement(StringBuffer buffer) {
    if (alias != null) {
      buffer.append(alias);
      buffer.append(LPAREN);
    } else {
      buffer.append(fProposal.getName());
      buffer.append(LPAREN);
    }
  }

  private ICompletionProposal[][] guessParameters(char[][] parameterNames)
      throws ModelException {
    // find matches in reverse order. Do this because people tend to declare
    // the variable meant for the last
    // parameter last. That is, local variables for the last parameter in
    // the method completion are more
    // likely to be closer to the point of code completion. As an example
    // consider a "delegation" completion:
    //
    // public void myMethod(int param1, int param2, int param3) {
    // someOtherObject.yourMethod(param1, param2, param3);
    // }
    //
    // The other consideration is giving preference to variables that have
    // not previously been used in this
    // code completion (which avoids
    // "someOtherObject.yourMethod(param1, param1, param1)";

    int count = parameterNames.length;
    fPositions = new Position[count];
    fChoices = new ICompletionProposal[count][];

    IParameter[] parameters = method.getParameters();

    for (int i = count - 1; i >= 0; i--) {
      String paramName = new String(parameterNames[i]);
      Position position = new Position(0, 0);

      ICompletionProposal[] argumentProposals = parameterProposals(
          parameters[i].getDefaultValue(), paramName, position,
          fFillBestGuess);

      fPositions[i] = position;
      fChoices[i] = argumentProposals;
    }

    return fChoices;
  }

  /*
   * @see ICompletionProposal#getSelection(IDocument)
   */
  public Point getSelection(IDocument document) {
    if (fSelectedRegion == null)
      return new Point(getReplacementOffset(), 0);

    return new Point(fSelectedRegion.getOffset(),
        fSelectedRegion.getLength());
  }

  private void openErrorDialog(Exception e) {
    Shell shell = getTextViewer().getTextWidget().getShell();
    MessageDialog.openError(shell, Messages.ParameterGuessingProposal_0,
        e.getMessage());
  }

  private void ensurePositionCategoryInstalled(final IDocument document,
      LinkedModeModel model) {
    if (!document.containsPositionCategory(getCategory())) {
      document.addPositionCategory(getCategory());
      fUpdater = new InclusivePositionUpdater(getCategory());
      document.addPositionUpdater(fUpdater);

      model.addLinkingListener(new ILinkedModeListener() {

        /*
         * @see
         * org.eclipse.jface.text.link.ILinkedModeListener#left(org.
         * eclipse.jface.text.link.LinkedModeModel, int)
         */
        public void left(LinkedModeModel environment, int flags) {
          ensurePositionCategoryRemoved(document);
        }

        public void suspend(LinkedModeModel environment) {
        }

        public void resume(LinkedModeModel environment, int flags) {
        }
      });
    }
  }

  private void ensurePositionCategoryRemoved(IDocument document) {
    if (document.containsPositionCategory(getCategory())) {
      try {
        document.removePositionCategory(getCategory());
      } catch (BadPositionCategoryException e) {
        // ignore
      }
      document.removePositionUpdater(fUpdater);
    }
  }

  private String getCategory() {
    return "ParameterGuessingProposal_" + toString(); //$NON-NLS-1$
  }

  /**
   * Returns the matches for the type and name argument, ordered by match
   * quality.
   *
   * @param expectedType
   *            - the qualified type of the parameter we are trying to match
   * @param paramName
   *            - the name of the parameter (used to find similarly named
   *            matches)
   * @param pos
   * @param suggestions
   *            the suggestions or <code>null</code>
   * @param fillBestGuess
   * @return returns the name of the best match, or <code>null</code> if no
   *         match found
   * @throws JavaModelException
   *             if it fails
   */
  public ICompletionProposal[] parameterProposals(String initialValue,
      String paramName, Position pos, boolean fillBestGuess)
      throws ModelException {
    List<String> typeMatches = new ArrayList<String>();
    if (initialValue != null) {
      typeMatches.add(initialValue);
    }
    ICompletionProposal[] ret = new ICompletionProposal[typeMatches.size()];
    int i = 0;
    int replacementLength = 0;
    for (Iterator<String> it = typeMatches.iterator(); it.hasNext();) {
      String name = it.next();
      if (i == 0) {
        replacementLength = name.length();
      }

      final char[] triggers = new char[1];
      triggers[triggers.length - 1] = ';';

      ret[i++] = new PositionBasedCompletionProposal(name, pos,
          replacementLength, getImage(), name, null, null, triggers);
    }
    if (!fillBestGuess) {
      // insert a proposal with the argument name
      ICompletionProposal[] extended = new ICompletionProposal[ret.length + 1];
      System.arraycopy(ret, 0, extended, 1, ret.length);
      extended[0] = new PositionBasedCompletionProposal(paramName, pos,
          replacementLength/* paramName.length() */, null, paramName,
          null, null, NO_TRIGGERS);
      return extended;
    }
    return ret;
  }

  @Override
  public void setReplacementOffset(int replacementOffset) {
    int oldReplacementOffset = getReplacementOffset();
    if (fPositions != null && fPositions.length > 0) {
      for (Position position : fPositions) {
        position.offset = position.offset
            + (replacementOffset - oldReplacementOffset);
      }
    }

    super.setReplacementOffset(replacementOffset);
  }

  public Object getExtraInfo() {
    return extraInfo;
  }
}
TOP

Related Classes of org.eclipse.php.internal.ui.editor.contentassist.ParameterGuessingProposal

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.