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 /** 40 * Returns the {@link javax.lang.model.element.Modifier} for the given token kind, or {@code 41 * null}. 42 */ getModifier(TokenKind kind)43 private static Modifier getModifier(TokenKind kind) { 44 if (kind == null) { 45 return null; 46 } 47 switch (kind) { 48 case PUBLIC: 49 return Modifier.PUBLIC; 50 case PROTECTED: 51 return Modifier.PROTECTED; 52 case PRIVATE: 53 return Modifier.PRIVATE; 54 case ABSTRACT: 55 return Modifier.ABSTRACT; 56 case STATIC: 57 return Modifier.STATIC; 58 case DEFAULT: 59 return Modifier.DEFAULT; 60 case FINAL: 61 return Modifier.FINAL; 62 case TRANSIENT: 63 return Modifier.TRANSIENT; 64 case VOLATILE: 65 return Modifier.VOLATILE; 66 case SYNCHRONIZED: 67 return Modifier.SYNCHRONIZED; 68 case NATIVE: 69 return Modifier.NATIVE; 70 case STRICTFP: 71 return Modifier.STRICTFP; 72 default: 73 return null; 74 } 75 } 76 77 /** Reorders all modifiers in the given text to be in JLS order. */ reorderModifiers(String text)78 static JavaInput reorderModifiers(String text) throws FormatterException { 79 return reorderModifiers( 80 new JavaInput(text), ImmutableList.of(Range.closedOpen(0, text.length()))); 81 } 82 83 /** 84 * Reorders all modifiers in the given text and within the given character ranges to be in JLS 85 * order. 86 */ reorderModifiers(JavaInput javaInput, Collection<Range<Integer>> characterRanges)87 static JavaInput reorderModifiers(JavaInput javaInput, Collection<Range<Integer>> characterRanges) 88 throws FormatterException { 89 if (javaInput.getTokens().isEmpty()) { 90 // There weren't any tokens, possible because of a lexing error. 91 // Errors about invalid input will be reported later after parsing. 92 return javaInput; 93 } 94 RangeSet<Integer> tokenRanges = javaInput.characterRangesToTokenRanges(characterRanges); 95 Iterator<? extends Token> it = javaInput.getTokens().iterator(); 96 TreeRangeMap<Integer, String> replacements = TreeRangeMap.create(); 97 while (it.hasNext()) { 98 Token token = it.next(); 99 if (!tokenRanges.contains(token.getTok().getIndex())) { 100 continue; 101 } 102 Modifier mod = asModifier(token); 103 if (mod == null) { 104 continue; 105 } 106 107 List<Token> modifierTokens = new ArrayList<>(); 108 List<Modifier> mods = new ArrayList<>(); 109 110 int begin = token.getTok().getPosition(); 111 mods.add(mod); 112 modifierTokens.add(token); 113 114 int end = -1; 115 while (it.hasNext()) { 116 token = it.next(); 117 mod = asModifier(token); 118 if (mod == null) { 119 break; 120 } 121 mods.add(mod); 122 modifierTokens.add(token); 123 end = token.getTok().getPosition() + token.getTok().length(); 124 } 125 126 if (!Ordering.natural().isOrdered(mods)) { 127 Collections.sort(mods); 128 StringBuilder replacement = new StringBuilder(); 129 for (int i = 0; i < mods.size(); i++) { 130 if (i > 0) { 131 addTrivia(replacement, modifierTokens.get(i).getToksBefore()); 132 } 133 replacement.append(mods.get(i).toString()); 134 if (i < (modifierTokens.size() - 1)) { 135 addTrivia(replacement, modifierTokens.get(i).getToksAfter()); 136 } 137 } 138 replacements.put(Range.closedOpen(begin, end), replacement.toString()); 139 } 140 } 141 return applyReplacements(javaInput, replacements); 142 } 143 addTrivia(StringBuilder replacement, ImmutableList<? extends Tok> toks)144 private static void addTrivia(StringBuilder replacement, ImmutableList<? extends Tok> toks) { 145 for (Tok tok : toks) { 146 replacement.append(tok.getText()); 147 } 148 } 149 150 /** 151 * Returns the given token as a {@link javax.lang.model.element.Modifier}, or {@code null} if it 152 * is not a modifier. 153 */ asModifier(Token token)154 private static Modifier asModifier(Token token) { 155 return getModifier(((JavaInput.Tok) token.getTok()).kind()); 156 } 157 158 /** Applies replacements to the given string. */ applyReplacements( JavaInput javaInput, TreeRangeMap<Integer, String> replacementMap)159 private static JavaInput applyReplacements( 160 JavaInput javaInput, TreeRangeMap<Integer, String> replacementMap) throws FormatterException { 161 // process in descending order so the replacement ranges aren't perturbed if any replacements 162 // differ in size from the input 163 Map<Range<Integer>, String> ranges = replacementMap.asDescendingMapOfRanges(); 164 if (ranges.isEmpty()) { 165 return javaInput; 166 } 167 StringBuilder sb = new StringBuilder(javaInput.getText()); 168 for (Entry<Range<Integer>, String> entry : ranges.entrySet()) { 169 Range<Integer> range = entry.getKey(); 170 sb.replace(range.lowerEndpoint(), range.upperEndpoint(), entry.getValue()); 171 } 172 return new JavaInput(sb.toString()); 173 } 174 } 175