• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy
6  * of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */
16 
17 package com.google.googlejavaformat.java;
18 
19 import com.google.common.collect.ImmutableList;
20 import com.google.common.collect.Ordering;
21 import com.google.common.collect.Range;
22 import com.google.common.collect.RangeSet;
23 import com.google.common.collect.TreeRangeMap;
24 import com.google.googlejavaformat.Input.Tok;
25 import com.google.googlejavaformat.Input.Token;
26 import com.sun.tools.javac.parser.Tokens.TokenKind;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Map.Entry;
34 import javax.lang.model.element.Modifier;
35 
36 /** Fixes sequences of modifiers to be in JLS order. */
37 final class ModifierOrderer {
38 
39   /** Reorders all modifiers in the given text to be in JLS order. */
reorderModifiers(String text)40   static JavaInput reorderModifiers(String text) throws FormatterException {
41     return reorderModifiers(
42         new JavaInput(text), ImmutableList.of(Range.closedOpen(0, text.length())));
43   }
44 
45   /**
46    * Reorders all modifiers in the given text and within the given character ranges to be in JLS
47    * order.
48    */
reorderModifiers(JavaInput javaInput, Collection<Range<Integer>> characterRanges)49   static JavaInput reorderModifiers(JavaInput javaInput, Collection<Range<Integer>> characterRanges)
50       throws FormatterException {
51     if (javaInput.getTokens().isEmpty()) {
52       // There weren't any tokens, possible because of a lexing error.
53       // Errors about invalid input will be reported later after parsing.
54       return javaInput;
55     }
56     RangeSet<Integer> tokenRanges = javaInput.characterRangesToTokenRanges(characterRanges);
57     Iterator<? extends Token> it = javaInput.getTokens().iterator();
58     TreeRangeMap<Integer, String> replacements = TreeRangeMap.create();
59     while (it.hasNext()) {
60       Token token = it.next();
61       if (!tokenRanges.contains(token.getTok().getIndex())) {
62         continue;
63       }
64       Modifier mod = asModifier(token);
65       if (mod == null) {
66         continue;
67       }
68 
69       List<Token> modifierTokens = new ArrayList<>();
70       List<Modifier> mods = new ArrayList<>();
71 
72       int begin = token.getTok().getPosition();
73       mods.add(mod);
74       modifierTokens.add(token);
75 
76       int end = -1;
77       while (it.hasNext()) {
78         token = it.next();
79         mod = asModifier(token);
80         if (mod == null) {
81           break;
82         }
83         mods.add(mod);
84         modifierTokens.add(token);
85         end = token.getTok().getPosition() + token.getTok().length();
86       }
87 
88       if (!Ordering.natural().isOrdered(mods)) {
89         Collections.sort(mods);
90         StringBuilder replacement = new StringBuilder();
91         for (int i = 0; i < mods.size(); i++) {
92           if (i > 0) {
93             addTrivia(replacement, modifierTokens.get(i).getToksBefore());
94           }
95           replacement.append(mods.get(i));
96           if (i < (modifierTokens.size() - 1)) {
97             addTrivia(replacement, modifierTokens.get(i).getToksAfter());
98           }
99         }
100         replacements.put(Range.closedOpen(begin, end), replacement.toString());
101       }
102     }
103     return applyReplacements(javaInput, replacements);
104   }
105 
addTrivia(StringBuilder replacement, ImmutableList<? extends Tok> toks)106   private static void addTrivia(StringBuilder replacement, ImmutableList<? extends Tok> toks) {
107     for (Tok tok : toks) {
108       replacement.append(tok.getText());
109     }
110   }
111 
112   /**
113    * Returns the given token as a {@link javax.lang.model.element.Modifier}, or {@code null} if it
114    * is not a modifier.
115    */
asModifier(Token token)116   private static Modifier asModifier(Token token) {
117     TokenKind kind = ((JavaInput.Tok) token.getTok()).kind();
118     if (kind != null) {
119       switch (kind) {
120         case PUBLIC:
121           return Modifier.PUBLIC;
122         case PROTECTED:
123           return Modifier.PROTECTED;
124         case PRIVATE:
125           return Modifier.PRIVATE;
126         case ABSTRACT:
127           return Modifier.ABSTRACT;
128         case STATIC:
129           return Modifier.STATIC;
130         case DEFAULT:
131           return Modifier.DEFAULT;
132         case FINAL:
133           return Modifier.FINAL;
134         case TRANSIENT:
135           return Modifier.TRANSIENT;
136         case VOLATILE:
137           return Modifier.VOLATILE;
138         case SYNCHRONIZED:
139           return Modifier.SYNCHRONIZED;
140         case NATIVE:
141           return Modifier.NATIVE;
142         case STRICTFP:
143           return Modifier.STRICTFP;
144         default: // fall out
145       }
146     }
147     switch (token.getTok().getText()) {
148       case "non-sealed":
149         return Modifier.valueOf("NON_SEALED");
150       case "sealed":
151         return Modifier.valueOf("SEALED");
152       default:
153         return null;
154     }
155   }
156 
157   /** Applies replacements to the given string. */
applyReplacements( JavaInput javaInput, TreeRangeMap<Integer, String> replacementMap)158   private static JavaInput applyReplacements(
159       JavaInput javaInput, TreeRangeMap<Integer, String> replacementMap) throws FormatterException {
160     // process in descending order so the replacement ranges aren't perturbed if any replacements
161     // differ in size from the input
162     Map<Range<Integer>, String> ranges = replacementMap.asDescendingMapOfRanges();
163     if (ranges.isEmpty()) {
164       return javaInput;
165     }
166     StringBuilder sb = new StringBuilder(javaInput.getText());
167     for (Entry<Range<Integer>, String> entry : ranges.entrySet()) {
168       Range<Integer> range = entry.getKey();
169       sb.replace(range.lowerEndpoint(), range.upperEndpoint(), entry.getValue());
170     }
171     return new JavaInput(sb.toString());
172   }
173 }
174