• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.google.googlejavaformat.java;
16 
17 import com.google.common.base.Preconditions;
18 import com.google.common.collect.Range;
19 import com.google.googlejavaformat.java.SnippetFormatter.SnippetKind;
20 import java.util.ArrayList;
21 import java.util.List;
22 import org.eclipse.jdt.core.dom.ASTParser;
23 import org.eclipse.jdt.core.formatter.CodeFormatter;
24 import org.eclipse.jface.text.IRegion;
25 import org.eclipse.jface.text.Region;
26 import org.eclipse.text.edits.MultiTextEdit;
27 import org.eclipse.text.edits.ReplaceEdit;
28 import org.eclipse.text.edits.TextEdit;
29 
30 /** Runs the Google Java formatter on the given code. */
31 public class GoogleJavaFormatter extends CodeFormatter {
32 
33   private static final int INDENTATION_SIZE = 2;
34 
35   @Override
format( int kind, String source, int offset, int length, int indentationLevel, String lineSeparator)36   public TextEdit format(
37       int kind, String source, int offset, int length, int indentationLevel, String lineSeparator) {
38     IRegion[] regions = new IRegion[] {new Region(offset, length)};
39     return formatInternal(kind, source, regions, indentationLevel);
40   }
41 
42   @Override
format( int kind, String source, IRegion[] regions, int indentationLevel, String lineSeparator)43   public TextEdit format(
44       int kind, String source, IRegion[] regions, int indentationLevel, String lineSeparator) {
45     return formatInternal(kind, source, regions, indentationLevel);
46   }
47 
48   @Override
createIndentationString(int indentationLevel)49   public String createIndentationString(int indentationLevel) {
50     Preconditions.checkArgument(
51         indentationLevel >= 0,
52         "Indentation level cannot be less than zero. Given: %s",
53         indentationLevel);
54     int spaces = indentationLevel * INDENTATION_SIZE;
55     StringBuilder buf = new StringBuilder(spaces);
56     for (int i = 0; i < spaces; i++) {
57       buf.append(' ');
58     }
59     return buf.toString();
60   }
61 
62   /** Runs the Google Java formatter on the given source, with only the given ranges specified. */
formatInternal(int kind, String source, IRegion[] regions, int initialIndent)63   private TextEdit formatInternal(int kind, String source, IRegion[] regions, int initialIndent) {
64     try {
65       boolean includeComments =
66           (kind & CodeFormatter.F_INCLUDE_COMMENTS) == CodeFormatter.F_INCLUDE_COMMENTS;
67       kind &= ~CodeFormatter.F_INCLUDE_COMMENTS;
68       SnippetKind snippetKind;
69       switch (kind) {
70         case ASTParser.K_EXPRESSION:
71           snippetKind = SnippetKind.EXPRESSION;
72           break;
73         case ASTParser.K_STATEMENTS:
74           snippetKind = SnippetKind.STATEMENTS;
75           break;
76         case ASTParser.K_CLASS_BODY_DECLARATIONS:
77           snippetKind = SnippetKind.CLASS_BODY_DECLARATIONS;
78           break;
79         case ASTParser.K_COMPILATION_UNIT:
80           snippetKind = SnippetKind.COMPILATION_UNIT;
81           break;
82         default:
83           throw new IllegalArgumentException(String.format("Unknown snippet kind: %d", kind));
84       }
85       List<Replacement> replacements =
86           new SnippetFormatter()
87               .format(
88                   snippetKind, source, rangesFromRegions(regions), initialIndent, includeComments);
89       if (idempotent(source, regions, replacements)) {
90         // Do not create edits if there's no diff.
91         return null;
92       }
93       // Convert replacements to text edits.
94       return editFromReplacements(replacements);
95     } catch (IllegalArgumentException | FormatterException exception) {
96       // Do not format on errors.
97       return null;
98     }
99   }
100 
rangesFromRegions(IRegion[] regions)101   private List<Range<Integer>> rangesFromRegions(IRegion[] regions) {
102     List<Range<Integer>> ranges = new ArrayList<>();
103     for (IRegion region : regions) {
104       ranges.add(Range.closedOpen(region.getOffset(), region.getOffset() + region.getLength()));
105     }
106     return ranges;
107   }
108 
109   /** @return {@code true} if input and output texts are equal, else {@code false}. */
idempotent(String source, IRegion[] regions, List<Replacement> replacements)110   private boolean idempotent(String source, IRegion[] regions, List<Replacement> replacements) {
111     // This implementation only checks for single replacement.
112     if (replacements.size() == 1) {
113       Replacement replacement = replacements.get(0);
114       String output = replacement.getReplacementString();
115       // Entire source case: input = output, nothing changed.
116       if (output.equals(source)) {
117         return true;
118       }
119       // Single region and single replacement case: if they are equal, nothing changed.
120       if (regions.length == 1) {
121         Range<Integer> range = replacement.getReplaceRange();
122         String snippet = source.substring(range.lowerEndpoint(), range.upperEndpoint());
123         if (output.equals(snippet)) {
124           return true;
125         }
126       }
127     }
128     return false;
129   }
130 
editFromReplacements(List<Replacement> replacements)131   private TextEdit editFromReplacements(List<Replacement> replacements) {
132     // Split the replacements that cross line boundaries.
133     TextEdit edit = new MultiTextEdit();
134     for (Replacement replacement : replacements) {
135       Range<Integer> replaceRange = replacement.getReplaceRange();
136       edit.addChild(
137           new ReplaceEdit(
138               replaceRange.lowerEndpoint(),
139               replaceRange.upperEndpoint() - replaceRange.lowerEndpoint(),
140               replacement.getReplacementString()));
141     }
142     return edit;
143   }
144 }
145