blob: f7f610be7dc5241a8cffef41ae92d3e59a013b6a [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.google.googlejavaformat.java;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import com.google.common.collect.TreeRangeMap;
import com.google.googlejavaformat.Input.Tok;
import com.google.googlejavaformat.Input.Token;
import com.sun.tools.javac.parser.Tokens.TokenKind;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.lang.model.element.Modifier;
/** Fixes sequences of modifiers to be in JLS order. */
final class ModifierOrderer {
/**
* Returns the {@link javax.lang.model.element.Modifier} for the given token kind, or {@code
* null}.
*/
private static Modifier getModifier(TokenKind kind) {
if (kind == null) {
return null;
}
switch (kind) {
case PUBLIC:
return Modifier.PUBLIC;
case PROTECTED:
return Modifier.PROTECTED;
case PRIVATE:
return Modifier.PRIVATE;
case ABSTRACT:
return Modifier.ABSTRACT;
case STATIC:
return Modifier.STATIC;
case DEFAULT:
return Modifier.DEFAULT;
case FINAL:
return Modifier.FINAL;
case TRANSIENT:
return Modifier.TRANSIENT;
case VOLATILE:
return Modifier.VOLATILE;
case SYNCHRONIZED:
return Modifier.SYNCHRONIZED;
case NATIVE:
return Modifier.NATIVE;
case STRICTFP:
return Modifier.STRICTFP;
default:
return null;
}
}
/** Reorders all modifiers in the given text to be in JLS order. */
static JavaInput reorderModifiers(String text) throws FormatterException {
return reorderModifiers(
new JavaInput(text), ImmutableList.of(Range.closedOpen(0, text.length())));
}
/**
* Reorders all modifiers in the given text and within the given character ranges to be in JLS
* order.
*/
static JavaInput reorderModifiers(JavaInput javaInput, Collection<Range<Integer>> characterRanges)
throws FormatterException {
if (javaInput.getTokens().isEmpty()) {
// There weren't any tokens, possible because of a lexing error.
// Errors about invalid input will be reported later after parsing.
return javaInput;
}
RangeSet<Integer> tokenRanges = javaInput.characterRangesToTokenRanges(characterRanges);
Iterator<? extends Token> it = javaInput.getTokens().iterator();
TreeRangeMap<Integer, String> replacements = TreeRangeMap.create();
while (it.hasNext()) {
Token token = it.next();
if (!tokenRanges.contains(token.getTok().getIndex())) {
continue;
}
Modifier mod = asModifier(token);
if (mod == null) {
continue;
}
List<Token> modifierTokens = new ArrayList<>();
List<Modifier> mods = new ArrayList<>();
int begin = token.getTok().getPosition();
mods.add(mod);
modifierTokens.add(token);
int end = -1;
while (it.hasNext()) {
token = it.next();
mod = asModifier(token);
if (mod == null) {
break;
}
mods.add(mod);
modifierTokens.add(token);
end = token.getTok().getPosition() + token.getTok().length();
}
if (!Ordering.natural().isOrdered(mods)) {
Collections.sort(mods);
StringBuilder replacement = new StringBuilder();
for (int i = 0; i < mods.size(); i++) {
if (i > 0) {
addTrivia(replacement, modifierTokens.get(i).getToksBefore());
}
replacement.append(mods.get(i).toString());
if (i < (modifierTokens.size() - 1)) {
addTrivia(replacement, modifierTokens.get(i).getToksAfter());
}
}
replacements.put(Range.closedOpen(begin, end), replacement.toString());
}
}
return applyReplacements(javaInput, replacements);
}
private static void addTrivia(StringBuilder replacement, ImmutableList<? extends Tok> toks) {
for (Tok tok : toks) {
replacement.append(tok.getText());
}
}
/**
* Returns the given token as a {@link javax.lang.model.element.Modifier}, or {@code null} if it
* is not a modifier.
*/
private static Modifier asModifier(Token token) {
return getModifier(((JavaInput.Tok) token.getTok()).kind());
}
/** Applies replacements to the given string. */
private static JavaInput applyReplacements(
JavaInput javaInput, TreeRangeMap<Integer, String> replacementMap) throws FormatterException {
// process in descending order so the replacement ranges aren't perturbed if any replacements
// differ in size from the input
Map<Range<Integer>, String> ranges = replacementMap.asDescendingMapOfRanges();
if (ranges.isEmpty()) {
return javaInput;
}
StringBuilder sb = new StringBuilder(javaInput.getText());
for (Entry<Range<Integer>, String> entry : ranges.entrySet()) {
Range<Integer> range = entry.getKey();
sb.replace(range.lowerEndpoint(), range.upperEndpoint(), entry.getValue());
}
return new JavaInput(sb.toString());
}
}