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