Package org.eclipse.jst.jsp.core.internal.java

Source Code of org.eclipse.jst.jsp.core.internal.java.JSPTranslationExtension$PositionDelta

/*******************************************************************************
* Copyright (c) 2004, 2007 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:
*     IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jst.jsp.core.internal.java;

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

import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jst.jsp.core.internal.Logger;
import org.eclipse.jst.jsp.core.internal.regions.DOMJSPRegionContexts;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.UndoEdit;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;

import com.ibm.icu.util.StringTokenizer;


/**
* Adds the notion of IDocuments (jsp Document and java Document) Used for
* TextEdit translation
*
* @author pavery
*/
public class JSPTranslationExtension extends JSPTranslation {

  // for debugging
  private static final boolean DEBUG;
  static {
    String value = Platform.getDebugOption("org.eclipse.jst.jsp.core/debug/jsptranslation"); //$NON-NLS-1$
    DEBUG = value != null && value.equalsIgnoreCase("true"); //$NON-NLS-1$
  }

  // just a convenience data structure
  // to keep track of java position deltas
  private class PositionDelta {

    public boolean isDeleted = false;
    public int preOffset = 0;
    public int preLength = 0;
    public int postOffset = 0;
    public int postLength = 0;

    public PositionDelta(int preOffset, int preLength) {
      this.preOffset = preOffset;
      this.preLength = preLength;
    }

    public void setPostEditData(int postOffset, int postLength, boolean isDeleted) {
      this.postOffset = postOffset;
      this.postLength = postLength;
      this.isDeleted = isDeleted;
    }
  }

  private IDocument fJspDocument = null;
  private IDocument fJavaDocument = null;
  private CodeFormatter fCodeFormatter = null;

  public JSPTranslationExtension(IDocument jspDocument, IDocument javaDocument, IJavaProject javaProj, JSPTranslator translator) {
    super(javaProj, translator);
    fJspDocument = jspDocument;
    fJavaDocument = javaDocument;

    // make sure positions are added to Java and JSP documents
    // this is necessary for text edits
    addPositionsToDocuments();
  }

  public IDocument getJspDocument() {
    return fJspDocument;
  }

  public IDocument getJavaDocument() {
    return fJavaDocument;
  }

  public String getJavaText() {
    return getJavaDocument() != null ? getJavaDocument().get() : ""; //$NON-NLS-1$
  }

  /**
   * Returns a corresponding TextEdit for the JSP file given a TextEdit for
   * a Java file.
   *
   * @param javaEdit
   * @return the corresponding JSP edits (not applied to the document yet)
   */
  public TextEdit getJspEdit(TextEdit javaEdit) {

    if (javaEdit == null)
      return null;

    List jspEdits = new ArrayList();

    int offset = javaEdit.getOffset();
    int length = javaEdit.getLength();

    if (javaEdit instanceof MultiTextEdit && javaEdit.getChildren().length > 0) {

      IRegion r = TextEdit.getCoverage(getAllEdits(javaEdit));
      offset = r.getOffset();
      length = r.getLength();
    }

    // get java ranges that will be affected by the edit
    Position[] javaPositions = getJavaRanges(offset, length);

    // record position data before the change
    Position[] jspPositions = new Position[javaPositions.length];
    PositionDelta[] deltas = new PositionDelta[javaPositions.length];
    for (int i = 0; i < javaPositions.length; i++) {
      deltas[i] = new PositionDelta(javaPositions[i].offset, javaPositions[i].length);
      // isIndirect means the position doesn't actually exist as exact
      // text
      // mapping from java <-> jsp (eg. an import statement)
      if (!isIndirect(javaPositions[i].offset))
        jspPositions[i] = (Position) getJava2JspMap().get(javaPositions[i]);
    }

    if (DEBUG) {
      System.out.println("================================================"); //$NON-NLS-1$
      System.out.println("deltas:"); //$NON-NLS-1$
      String javaText = getJavaText();
      for (int i = 0; i < deltas.length; i++)
        System.out.println("pos[" + deltas[i].preOffset + ":" + deltas[i].preLength + "]" + javaText.substring(deltas[i].preOffset, deltas[i].preOffset + deltas[i].preLength)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      System.out.println("==============================================="); //$NON-NLS-1$
    }
    UndoEdit undo = null;
    // apply the edit to the java document
    try {
      undo = javaEdit.apply(getJavaDocument());
    }
    catch (MalformedTreeException e) {
      Logger.logException(e);
    }
    catch (BadLocationException e) {
      Logger.logException(e);
    }
    // now at this point Java positions are unreliable since they were
    // updated after applying java edit.

    String newJavaText = getJavaDocument().get();
    if (DEBUG)
      System.out.println("java post format text:\n" + newJavaText); //$NON-NLS-1$

    // record post edit data
    for (int i = 0; i < javaPositions.length; i++)
      deltas[i].setPostEditData(javaPositions[i].offset, javaPositions[i].length, javaPositions[i].isDeleted);

    // create appropriate text edits for deltas
    Position jspPos = null;
    String replaceText = ""; //$NON-NLS-1$
    for (int i = 0; i < deltas.length; i++) {
      jspPos = jspPositions[i];
      // can be null if it's an indirect mapping position
      // or if something was added into java that was not originally in
      // JSP (like a new import...)

      if (jspPos != null) {
        if (deltas[i].isDeleted) {
          jspEdits.add(new DeleteEdit(jspPos.offset, jspPos.length));
        }
        else {
          replaceText = newJavaText.substring(deltas[i].postOffset, deltas[i].postOffset + deltas[i].postLength);

          // get rid of pre and post white space or fine tuned
          // adjustment later.
          // fix text here...
          replaceText = fixJspReplaceText(replaceText, jspPos.offset);

          jspEdits.add(new ReplaceEdit(jspPos.offset, jspPos.length, replaceText));
        }
        if (DEBUG)
          debugReplace(deltas, jspPos, replaceText, i);
      }
      else {
        // the new Java text has no corresponding JSP position
        // possible new import?
        if (isImport(javaPositions[i].getOffset()) && replaceText.lastIndexOf("import ") != -1) { //$NON-NLS-1$
          replaceText = newJavaText.substring(deltas[i].postOffset, deltas[i].postOffset + deltas[i].postLength);
          String importText = replaceText.substring(replaceText.lastIndexOf("import "), replaceText.indexOf(";")); //$NON-NLS-1$ //$NON-NLS-2$
          // evenutally need to check if it's XML-JSP
          importText = "<%@page import=\"" + importText + "\" %>\n"; //$NON-NLS-1$ //$NON-NLS-2$
          jspEdits.add(new InsertEdit(0, importText));
        }
      }
    }
    TextEdit allJspEdits = createMultiTextEdit((TextEdit[]) jspEdits.toArray(new TextEdit[jspEdits.size()]));

    // https://bugs.eclipse.org/bugs/show_bug.cgi?id=105632
    // undo the java edit
    // (so the underlying Java document still represents what's in the
    // editor)
    if (undo != null) {
      try {
        undo.apply(getJavaDocument());
      }
      catch (MalformedTreeException e) {
        Logger.logException(e);
      }
      catch (BadLocationException e) {
        Logger.logException(e);
      }
    }

    return allJspEdits;
  }

  private String fixJspReplaceText(String replaceText, int jspOffset) {

    // result is the text inbetween the delimiters
    // eg.
    //
    // <% result
    // %>
    String result = replaceText.trim();
    String preDelimiterWhitespace = ""; //$NON-NLS-1$

    IDocument jspDoc = getJspDocument();
    if (jspDoc instanceof IStructuredDocument) {
      IStructuredDocument sDoc = (IStructuredDocument) jspDoc;
      IStructuredDocumentRegion[] regions = sDoc.getStructuredDocumentRegions(0, jspOffset);
      IStructuredDocumentRegion lastRegion = regions[regions.length - 1];

      // only specifically modify scriptlets
      if (lastRegion != null && lastRegion.getType() == DOMJSPRegionContexts.JSP_SCRIPTLET_OPEN) {
        for (int i = regions.length - 1; i >= 0; i--) {
          IStructuredDocumentRegion region = regions[i];

          // is there a better way to check whitespace?
          if (region.getType() == DOMRegionContext.XML_CONTENT && region.getFullText().trim().equals("")) { //$NON-NLS-1$

            preDelimiterWhitespace = region.getFullText();
            preDelimiterWhitespace = preDelimiterWhitespace.replaceAll("\r", ""); //$NON-NLS-1$ //$NON-NLS-2$
            preDelimiterWhitespace = preDelimiterWhitespace.replaceAll("\n", ""); //$NON-NLS-1$ //$NON-NLS-2$

            // need to determine indent for that first line...
            String initialIndent = getInitialIndent(result);

            // fix the first line of java code
            result = TextUtilities.getDefaultLineDelimiter(sDoc) + initialIndent + result;

            result = adjustIndent(result, preDelimiterWhitespace, TextUtilities.getDefaultLineDelimiter(sDoc));

            // add whitespace before last delimiter to match
            // it w/ the opening delimiter
            result = result + TextUtilities.getDefaultLineDelimiter(sDoc) + preDelimiterWhitespace;
            break;
          }
        }
      }
    }
    return result;
  }

  private String adjustIndent(String textBefore, String indent, String delim) {

    // first replace multiple indent with single indent
    // the triple indent occurs because the scriptlet code
    // actually occurs under:
    //
    // class
    // method
    // code
    //
    // in the translated java document
    // BUG188636 - just get indent info from code formatter
    String level1 = getCodeFormatter().createIndentationString(1);
    String level3 = getCodeFormatter().createIndentationString(3);
    String theOld = "\n" + level3; //$NON-NLS-1$
    String theNew = "\n" + level1; //$NON-NLS-1$
    textBefore = textBefore.replaceAll(theOld, theNew);

    // get indent after 2nd line break
    StringBuffer textAfter = new StringBuffer();
    // will this work on mac?
    textBefore = textBefore.replaceAll("\r", ""); //$NON-NLS-1$ //$NON-NLS-2$
    StringTokenizer st = new StringTokenizer(textBefore, "\n", true); //$NON-NLS-1$
    while (st.hasMoreTokens()) {
      String tok = st.nextToken();
      if (tok.equals("\n")) { //$NON-NLS-1$
        textAfter.append(delim);
      }
      else {
        // prepend each line w/ specified indent
        textAfter.append(indent);
        textAfter.append(tok);
      }
    }
    return textAfter.toString();

  }

  private String getInitialIndent(String result) {
    // BUG188636 - just get initial indent from code formatter
    String indent = getCodeFormatter().createIndentationString(1);
    // // get indent after 2nd line break
    // String indent = ""; //$NON-NLS-1$
    // StringTokenizer st = new StringTokenizer(result, "\r\n", false);
    // //$NON-NLS-1$
    // if (st.countTokens() > 1) {
    // String tok = st.nextToken();
    // tok = st.nextToken();
    // int index = 0;
    // if (tok != null) {
    // while (tok.charAt(index) == ' ' || tok.charAt(index) == '\t') {
    // indent += tok.charAt(index);
    // index++;
    // }
    // }
    // }
    return indent;
  }

  private CodeFormatter getCodeFormatter() {
    if (fCodeFormatter == null)
      fCodeFormatter = ToolFactory.createCodeFormatter(null);
    return fCodeFormatter;
  }


  /**
   * Combines an array of edits into one MultiTextEdit (with the appropriate
   * coverage region)
   *
   * @param edits
   * @return
   */
  private TextEdit createMultiTextEdit(TextEdit[] edits) {

    if (edits.length == 0)
      return new MultiTextEdit();

    /* should not specify a limited region because other edits outside
     * these original edits might be added later.
     */
    MultiTextEdit multiEdit = new MultiTextEdit();
    for (int i = 0; i < edits.length; i++) {
      addToMultiEdit(edits[i], multiEdit);
    }
    return multiEdit;
  }


  private void addToMultiEdit(TextEdit edit, MultiTextEdit multiEdit) {

    // check for overlap here
    // discard overlapping edits..
    // possible exponential performance hit... need a better way...
    TextEdit[] children = multiEdit.getChildren();
    for (int i = 0; i < children.length; i++) {
      if (children[i].covers(edit))
        // don't add
        return;
    }
    multiEdit.addChild(edit);
  }


  /**
   * @param translation
   */
  private void addPositionsToDocuments() {

    // can be null if it's a NullJSPTranslation
    if (getJavaDocument() != null && getJspDocument() != null) {

      HashMap java2jsp = getJava2JspMap();
      Iterator it = java2jsp.keySet().iterator();
      Position javaPos = null;
      while (it.hasNext()) {
        javaPos = (Position) it.next();
        try {

          fJavaDocument.addPosition(javaPos);

        }
        catch (BadLocationException e) {
          if (DEBUG) {
            System.out.println("tyring to add Java Position:[" + javaPos.offset + ":" + javaPos.length + "] to " + getJavaPath()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$         
            // System.out.println("substring :[" +
            // fJavaDocument.get().substring(javaPos.offset) +
            // "]"); //$NON-NLS-1$ //$NON-NLS-2$
            Logger.logException(e);
          }
        }

        try {

          fJspDocument.addPosition((Position) java2jsp.get(javaPos));

        }
        catch (BadLocationException e) {
          if (DEBUG) {
            System.out.println("tyring to add JSP Position:[" + ((Position) java2jsp.get(javaPos)).offset + ":" + ((Position) java2jsp.get(javaPos)).length + "] to " + getJavaPath()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            Logger.logException(e);
          }
        }
      }
    }
  }

  /**
   * Recursively gets all child edits
   *
   * @param javaEdit
   * @return all child edits
   */
  private TextEdit[] getAllEdits(TextEdit javaEdit) {

    List result = new ArrayList();
    if (javaEdit instanceof MultiTextEdit) {
      TextEdit[] children = javaEdit.getChildren();
      for (int i = 0; i < children.length; i++)
        result.addAll(Arrays.asList(getAllEdits(children[i])));
    }
    else
      result.add(javaEdit);
    return (TextEdit[]) result.toArray(new TextEdit[result.size()]);
  }

  /**
   * @param deltas
   * @param jspPos
   * @param replaceText
   * @param jspText
   * @param i
   */
  private void debugReplace(PositionDelta[] deltas, Position jspPos, String replaceText, int i) {
    String jspChunk;
    jspChunk = getJspDocument().get().substring(jspPos.offset, jspPos.offset + jspPos.length);
    if (!deltas[i].isDeleted) {
      System.out.println("replacing:"); //$NON-NLS-1$
      System.out.println("jsp:[" + jspChunk + "]"); //$NON-NLS-1$ //$NON-NLS-2$
      System.out.println("w/ :[" + replaceText + "]"); //$NON-NLS-1$ //$NON-NLS-2$
      System.out.println("--------------------------------"); //$NON-NLS-1$
    }
  }
}
TOP

Related Classes of org.eclipse.jst.jsp.core.internal.java.JSPTranslationExtension$PositionDelta

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.