Package krasa.formatter

Source Code of krasa.formatter.JsniFormattingUtil$JsniJavaRefReplacementResult

/*******************************************************************************
* Copyright 2011 Google Inc. All Rights Reserved.
*
* 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
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package krasa.formatter;

import com.google.gwt.eclipse.core.editors.java.GWTPartitions;
import com.google.gwt.eclipse.core.validators.java.JsniParser;
import krasa.formatter.plugin.Range;
import krasa.formatter.settings.Settings;
import org.eclipse.jdt.core.formatter.IndentManipulation;
import org.eclipse.jdt.internal.formatter.DefaultCodeFormatter;
import org.eclipse.jface.text.*;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.wst.jsdt.core.ToolFactory;
import org.eclipse.wst.jsdt.core.formatter.CodeFormatter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Utility methods for formatting JSNI methods. This is not a full-blown JavaScript pretty-printer, but it does apply
* the correct outer indentation to JSNI blocks, to correct the JDT bug which slides them to the right.
*/
@SuppressWarnings("restriction")
public class JsniFormattingUtil {

    private static class JsniJavaRefReplacementResult {
        private String jsni;
        private Map<String, String> replacements;

        public JsniJavaRefReplacementResult(String jsni, Map<String, String> replacements) {
            this.jsni = jsni;
            this.replacements = replacements;
        }

        public String getJsni() {
            return jsni;
        }

        public Map<String, String> getReplacements() {
            return replacements;
        }
    }

    /**
     * Returns a text edit that formats the given document according to the given settings.
     *
     * @param document                  The document to format.
     * @param javaFormattingPrefs       The formatting preferences for Java, used to determine the method level indentation.
     * @param javaScriptFormattingPrefs The formatting preferences for JavaScript. See org.eclipse.wst.jsdt.internal.formatter
     *                                  .DefaultCodeFormatterOptions and org.eclipse.wst.jsdt.core.formatter.DefaultCodeFormatterConstants
     * @param originalJsniMethods       The original jsni methods to use if the formatter fails to format the method. The original jsni
     *                                  Strings must be in the same order that the jsni methods occur in the document. This is to work around
     *                                  the Java formatter blasting the jsni tabbing for the format-on-save action. May be null.
     * @return A text edit that when applied to the document, will format the jsni methods.
     */
    @SuppressWarnings("unchecked")
    public static TextEdit format(IDocument document, Map javaFormattingPrefs, Map javaScriptFormattingPrefs,
                                  Range range) {
        TextEdit combinedEdit = new MultiTextEdit();
        ITypedRegion[] regions = computePartitioning(document, range);

        // Format all JSNI blocks in the document
        int i = 0;
        for (ITypedRegion region : regions) {
            if (region.getType().equals(GWTPartitions.JSNI_METHOD)) {
                String originalJsniMethod = null;
                TextEdit edit = format(document, new TypedPosition(region), javaFormattingPrefs,
                        javaScriptFormattingPrefs, originalJsniMethod);
                if (edit != null) {
                    combinedEdit.addChild(edit);
                }
                i++;
            }
        }
        return combinedEdit;

    }

    private static ITypedRegion[] computePartitioning(IDocument document, Range range) {
        ArrayList<ITypedRegion> iTypedRegions = new ArrayList<ITypedRegion>();
        String str = document.get();
        String prefix = "/*-";
        String postfix = "-*/";
        int startIndex = 0;
        int endIndex = 0;

        while (startIndex != -1) {
            startIndex = str.indexOf(prefix, startIndex);
            endIndex = str.indexOf(postfix, startIndex);

            if (startIndex != -1 && endIndex != -1) {
                endIndex = endIndex + 3;
                if (isInRange(range, startIndex, endIndex)) {
                    iTypedRegions.add(new TypedRegion(startIndex, endIndex - startIndex, GWTPartitions.JSNI_METHOD));
                }
                startIndex += prefix.length();
            }
        }
        return iTypedRegions.toArray(new ITypedRegion[iTypedRegions.size()]);

    }

    private static boolean isInRange(Range range, int startIndex, int endIndex) {
        boolean b = range.getStartOffset() < startIndex && range.getEndOffset() > endIndex;
        boolean b1 = startIndex < range.getEndOffset() && range.getEndOffset() < endIndex;
        boolean b2 = startIndex < range.getStartOffset() && range.getStartOffset() < endIndex;
        return range.isWholeFile() || b || b1 || b2;
    }

    public static TextEdit format(IDocument document, TypedPosition partition, Map<String, String> javaFormattingPrefs,
                                  Map<String, String> javaScriptFormattingPrefs, String original) {
        try {
            // Extract the JSNI block out of the document
            int offset = partition.getOffset();
            int length = partition.getLength();

            // Determine the line delimiter, indent string, and tab/indent widths
            String lineDelimiter = Settings.LINE_SEPARATOR;
            int tabWidth = IndentManipulation.getTabWidth(javaFormattingPrefs);
            int indentWidth = IndentManipulation.getIndentWidth(javaFormattingPrefs);

            // Get indentation level of the first line of the JSNI block (this should
            // be the line containing the JSNI method declaration)
            int methodDeclarationOffset = getMethodDeclarationOffset(document, offset);

            int jsniLine1 = document.getLineOfOffset(methodDeclarationOffset);
            int methodIndentLevel = getLineIndentLevel(document, jsniLine1, tabWidth, indentWidth);
            DefaultCodeFormatter defaultCodeFormatter = new DefaultCodeFormatter(javaFormattingPrefs);
            String indentLine = defaultCodeFormatter.createIndentationString(methodIndentLevel);

            // Extract the JSNI body out of the block and split it up by line
            String jsniSource = document.get(offset, length);
            String body = JsniParser.extractMethodBody(jsniSource);

            String formattedJs;

            // JSNI Java references mess up the JS formatter, so replace them
            // with place holder values
            JsniJavaRefReplacementResult replacementResults = replaceJsniJavaRefs(body);
            body = replacementResults.getJsni();
            CodeFormatter codeFormatter = ToolFactory.createCodeFormatter(javaScriptFormattingPrefs);

            TextEdit formatEdit = codeFormatter.format(CodeFormatter.K_STATEMENTS, body, 0, body.length(),
                    methodIndentLevel + 1, lineDelimiter);

            if (formatEdit != null) {

                body = restoreJsniJavaRefs(replacementResults);

                Document d = new Document(body);
                formatEdit.apply(d);

                formattedJs = d.get();

                if (!formattedJs.startsWith(lineDelimiter)) {
                    formattedJs = lineDelimiter + formattedJs;
                }

                if (!formattedJs.endsWith(lineDelimiter)) {
                    formattedJs = formattedJs + lineDelimiter;
                }

                formattedJs = formattedJs + indentLine;

                formattedJs = "/*-{" + formattedJs + "}-*/";

            } else {

                if (original == null) {
                    return null;
                }

                formattedJs = original; // formatting failed, use the original string
            }

            return new ReplaceEdit(offset, length, formattedJs);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static int getMethodDeclarationOffset(IDocument document, int offset) {
        int methodDeclarationOffset = offset;

        // jsniMethodWithDeclarationLineThatWraps
        String s = document.get();
        int i = s.lastIndexOf("\n", offset);
        String substring = s.substring(i + 1, offset);
        substring = substring.trim();
        if (!substring.isEmpty() && !substring.contains("(")) {
            methodDeclarationOffset = i - 2;
        }
        return methodDeclarationOffset;
    }

    private static int getLineIndentLevel(IDocument document, int line, int tabWidth, int indentWidth)
            throws BadLocationException {
        int lineOffset = document.getLineOffset(line);
        return getLineIndentLevel(document.get(lineOffset, document.getLineLength(line)), tabWidth, indentWidth);
    }

    private static int getLineIndentLevel(String line, int tabWidth, int indentWidth) {
        return IndentManipulation.measureIndentUnits(line, tabWidth, indentWidth);
    }

    private static String makeJsToken(String s) {
        int hashCode = s.hashCode();
        // js variable names can't have negative signs in them
        String jsToken = "_" + (hashCode < 0 ? "N" + Math.abs(hashCode) : hashCode);

        // pad the hash so it's the same length as the original reference so that
        // things like formatting line wrap works
        while (jsToken.length() < s.length()) {
            jsToken = jsToken + "_";
        }
        return jsToken;
    }

    private static JsniJavaRefReplacementResult replaceJsniJavaRefs(String jsni) {

        Map<String, String> replacements = new HashMap<String, String>();

        Pattern p = Pattern.compile("@[a-zA-Z0-9._$]+::[a-zA-Z0-9_$]+(\\(.*?\\)\\(.*?\\))?");

        Matcher m = p.matcher(jsni);

        while (m.find()) {
            int start = m.start();
            int end = m.end();

            String ref = jsni.substring(start, end);
            String jsToken = makeJsToken(ref);

            // if the map already contains the js token, and the token's original jsni
            // ref is not the one we've found, js-tokenize the token
            while (replacements.containsKey(jsToken) && !replacements.get(jsToken).equals(ref)) {
                jsToken = makeJsToken(jsToken);
            }

            replacements.put(jsToken, ref);
        }

        for (Entry<String, String> kvp : replacements.entrySet()) {
            jsni = jsni.replace(kvp.getValue(), kvp.getKey());
        }

        return new JsniJavaRefReplacementResult(jsni, replacements);
    }

    private static String restoreJsniJavaRefs(JsniJavaRefReplacementResult result) {

        String jsni = result.getJsni();
        for (Entry<String, String> kvp : result.getReplacements().entrySet()) {
            jsni = jsni.replace(kvp.getKey(), kvp.getValue());
        }

        return jsni;
    }
}
TOP

Related Classes of krasa.formatter.JsniFormattingUtil$JsniJavaRefReplacementResult

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.