Skip to content

Commit

Permalink
Add AndroidImportOrganizer to support Android import formatting
Browse files Browse the repository at this point in the history
Supports both android-static-first and android-static-last as the
Android Code Style does not specify how static imports should be
ordered relative to non-static imports.

Refactors the ImportOrganizer interface and existing implementations
to provide a better abstraction of an import and the results of
organizing the imports.

Added tests for BasicImportOrganizer to ensure that behavior did
not change.

RELNOTES: Updated options so order can be selected using:
   -XepPatchImportOrder:android-static-first
   -XepPatchImportOrder:android-static-last

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=155603065
  • Loading branch information
paulduffin authored and ronshapiro committed May 24, 2017
1 parent 918e701 commit 0c7f039
Show file tree
Hide file tree
Showing 11 changed files with 701 additions and 89 deletions.
Expand Up @@ -32,6 +32,10 @@ public static ImportOrganizer getImportOrganizer(String importOrder) {
return ImportOrganizer.STATIC_FIRST_ORGANIZER; return ImportOrganizer.STATIC_FIRST_ORGANIZER;
case "static-last": case "static-last":
return ImportOrganizer.STATIC_LAST_ORGANIZER; return ImportOrganizer.STATIC_LAST_ORGANIZER;
case "android-static-first":
return ImportOrganizer.ANDROID_STATIC_FIRST_ORGANIZER;
case "android-static-last":
return ImportOrganizer.ANDROID_STATIC_LAST_ORGANIZER;
default: default:
throw new IllegalStateException("Unknown import order: '" + importOrder + "'"); throw new IllegalStateException("Unknown import order: '" + importOrder + "'");
} }
Expand Down
@@ -0,0 +1,139 @@
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.errorprone.apply;

import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;

/**
* Organizes imports based on Android Code Style
*
* <p>As the style rules do not specify where static imports are placed this supports first or last.
*
* <p>The imports are partitioned into groups in two steps, each step sub-partitions all groups from
* the previous step. The steps are:
*
* <ol>
* <li>Split into static and non-static, the static imports come before or after the non static
* imports depending on the {@link StaticOrder} specified.
* <li>The groups are then partitioned based on a prefix of the type, the groups are ordered by
* prefix as follows:
* <ol>
* <li>{@code android.}
* <li>{@code com.android.}
* <li>A group for each root package, in alphabetical order.
* <li>{@code java.}
* <li>{@code javax.}
* </ol>
* </ol>
*
* <p>Each group is separate from the previous/next groups with a blank line.
*/
class AndroidImportOrganizer implements ImportOrganizer {

private static final String ANDROID = "android";

private static final String COM_ANDROID = "com.android";

private static final String JAVA = "java";

private static final String JAVAX = "javax";

private static final ImmutableSet<String> SPECIAL_ROOTS =
ImmutableSet.of(ANDROID, COM_ANDROID, JAVA, JAVAX);

private final StaticOrder order;

AndroidImportOrganizer(StaticOrder order) {
this.order = order;
}

@Override
public OrganizedImports organizeImports(List<Import> imports) {
OrganizedImports organized = new OrganizedImports();

// Group into static and non-static.
Map<Boolean, List<Import>> partionedByStatic =
imports.stream().collect(Collectors.partitioningBy(Import::isStatic));

for (Boolean key : order.groupOrder()) {
organizePartition(organized, partionedByStatic.get(key));
}

return organized;
}

private void organizePartition(OrganizedImports organized, List<Import> imports) {

Map<String, ImmutableSortedSet<Import>> groupedByRoot =
imports
.stream()
.collect(
Collectors.groupingBy(
// Group by root package.
AndroidImportOrganizer::rootPackage,
// Ensure that the results are sorted.
TreeMap::new,
// Each group is a set sorted by type.
toImmutableSortedSet(Comparator.comparing(Import::getType))));

// Get the third party roots by removing the roots that are handled specially and sorting.
Set<String> thirdParty =
groupedByRoot
.keySet()
.stream()
.filter(r -> !SPECIAL_ROOTS.contains(r))
.collect(toImmutableSortedSet(Ordering.natural()));

// Construct a list of the possible roots in the correct order.
List<String> roots =
ImmutableList.<String>builder()
.add(ANDROID)
.add(COM_ANDROID)
.addAll(thirdParty)
.add(JAVA)
.add(JAVAX)
.build();

organized.addGroups(groupedByRoot, roots);
}

private static String rootPackage(Import anImport) {
String type = anImport.getType();

if (type.startsWith("com.android.")) {
return "com.android";
}

int index = type.indexOf('.');
if (index == -1) {
// Treat the default package as if it has an empty root.
return "";
} else {
return type.substring(0, index);
}
}
}
Expand Up @@ -15,84 +15,39 @@
*/ */
package com.google.errorprone.apply; package com.google.errorprone.apply;


import com.google.common.collect.ComparisonChain; import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering; import com.google.common.collect.ImmutableSortedSet;
import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.SortedSet; import java.util.Map;
import java.util.TreeSet; import java.util.stream.Collectors;


/** /**
* Sorts imports according to a supplied {@link Comparator} and separates static and non-static with * Sorts imports according to a supplied {@link StaticOrder} and separates static and non-static
* a blank line. * with a blank line.
*/ */
public class BasicImportOrganizer implements ImportOrganizer { class BasicImportOrganizer implements ImportOrganizer {

private final Comparator<String> comparator;

BasicImportOrganizer(Comparator<String> comparator) {
this.comparator = comparator;
}

private static boolean isStatic(String importString) {
return importString.startsWith("import static ");
}


/** private final StaticOrder order;
* A {@link Comparator} that sorts import statements so that all static imports come before all
* non-static imports and otherwise sorted alphabetically.
*/
static Comparator<String> staticFirst() {
return new Ordering<String>() {
@Override
public int compare(String s1, String s2) {
return ComparisonChain.start()
.compareTrueFirst(isStatic(s1), isStatic(s2))
.compare(s1, s2)
.result();
}
};
}


/** BasicImportOrganizer(StaticOrder order) {
* A {@link Comparator} that sorts import statements so that all static imports come after all this.order = order;
* non-static imports and otherwise sorted alphabetically.
*/
static Comparator<String> staticLast() {
return new Ordering<String>() {
@Override
public int compare(String s1, String s2) {
return ComparisonChain.start()
.compareFalseFirst(isStatic(s1), isStatic(s2))
.compare(s1, s2)
.result();
}
};
} }


@Override @Override
public Iterable<String> organizeImports(Iterable<String> importStrings) { public OrganizedImports organizeImports(List<Import> imports) {
SortedSet<String> sorted = new TreeSet<>(comparator);
Iterables.addAll(sorted, importStrings); // Group into static and non-static. Each group is a set sorted by type.

Map<Boolean, ImmutableSortedSet<Import>> partionedByStatic =
List<String> organized = new ArrayList<>(); imports

.stream()
// output sorted imports, with a line break between static and non-static imports .collect(
boolean first = true; Collectors.partitioningBy(
boolean prevIsStatic = true; Import::isStatic, toImmutableSortedSet(Comparator.comparing(Import::getType))));
for (String importString : sorted) {
boolean isStatic = isStatic(importString); return new OrganizedImports()
if (!first && prevIsStatic != isStatic) { // Add groups, in the appropriate order.
// Add a blank line. .addGroups(partionedByStatic, order.groupOrder());
organized.add("");
}
organized.add(importString);
prevIsStatic = isStatic;
first = false;
}

return organized;
} }
} }

0 comments on commit 0c7f039

Please sign in to comment.